001    /*
002     * Copyright 2011-2012 UnboundID Corp.
003     *
004     * This program is free software; you can redistribute it and/or modify
005     * it under the terms of the GNU General Public License (GPLv2 only)
006     * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
007     * as published by the Free Software Foundation.
008     *
009     * This program is distributed in the hope that it will be useful,
010     * but WITHOUT ANY WARRANTY; without even the implied warranty of
011     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012     * GNU General Public License for more details.
013     *
014     * You should have received a copy of the GNU General Public License
015     * along with this program; if not, see <http://www.gnu.org/licenses>.
016     */
017    
018    package com.unboundid.scim.marshal.json;
019    
020    import com.unboundid.scim.data.BaseResource;
021    import com.unboundid.scim.data.BulkConfig;
022    import com.unboundid.scim.data.ResourceFactory;
023    import com.unboundid.scim.marshal.Unmarshaller;
024    import com.unboundid.scim.schema.ResourceDescriptor;
025    import com.unboundid.scim.sdk.BulkContentHandler;
026    import com.unboundid.scim.sdk.Debug;
027    import com.unboundid.scim.sdk.InvalidResourceException;
028    import com.unboundid.scim.sdk.Resources;
029    import com.unboundid.scim.sdk.SCIMException;
030    import com.unboundid.scim.sdk.ServerErrorException;
031    import org.json.JSONArray;
032    import org.json.JSONException;
033    import org.json.JSONObject;
034    import org.json.JSONTokener;
035    
036    import java.io.BufferedInputStream;
037    import java.io.File;
038    import java.io.FileInputStream;
039    import java.io.IOException;
040    import java.io.InputStream;
041    import java.util.ArrayList;
042    import java.util.Collections;
043    import java.util.List;
044    import java.util.concurrent.atomic.AtomicInteger;
045    
046    
047    
048    /**
049     * This class provides a SCIM object un-marshaller implementation to read SCIM
050     * objects from their JSON representation.
051     */
052    public class JsonUnmarshaller implements Unmarshaller
053    {
054      /**
055       * {@inheritDoc}
056       */
057      public <R extends BaseResource> R unmarshal(
058          final InputStream inputStream,
059          final ResourceDescriptor resourceDescriptor,
060          final ResourceFactory<R> resourceFactory) throws InvalidResourceException
061      {
062        try
063        {
064          final JSONObject jsonObject =
065              new JSONObject(new JSONTokener(inputStream));
066    
067          final JsonParser parser = new JsonParser();
068          return parser.unmarshal(jsonObject, resourceDescriptor, resourceFactory,
069                                  null);
070        }
071        catch(JSONException e)
072        {
073          throw new InvalidResourceException("Error while reading JSON: " +
074              e.getMessage(), e);
075        }
076      }
077    
078      /**
079       * {@inheritDoc}
080       */
081      public <R extends BaseResource> Resources<R> unmarshalResources(
082          final InputStream inputStream,
083          final ResourceDescriptor resourceDescriptor,
084          final ResourceFactory<R> resourceFactory) throws InvalidResourceException
085      {
086        try
087        {
088          final JsonParser parser = new JsonParser();
089          final JSONObject jsonObject =
090              new JSONObject(new JSONTokener(inputStream));
091    
092          int totalResults = 0;
093          if(jsonObject.has("totalResults"))
094          {
095            totalResults = jsonObject.getInt("totalResults");
096          }
097    
098          int startIndex = 1;
099          if(jsonObject.has("startIndex"))
100          {
101            startIndex = jsonObject.getInt("startIndex");
102          }
103    
104          final JSONArray schemas = jsonObject.optJSONArray("schemas");
105    
106          List<R> resources = Collections.emptyList();
107          if(jsonObject.has("Resources"))
108          {
109            JSONArray resourcesArray = jsonObject.getJSONArray("Resources");
110            resources = new ArrayList<R>(resourcesArray.length());
111            for(int i = 0; i < resourcesArray.length(); i++)
112            {
113              R resource =
114                  parser.unmarshal(resourcesArray.getJSONObject(i),
115                                   resourceDescriptor,
116                                   resourceFactory,
117                                   schemas);
118              resources.add(resource);
119            }
120          }
121    
122          return new Resources<R>(resources, totalResults, startIndex);
123        }
124        catch(JSONException e)
125        {
126          throw new InvalidResourceException("Error while reading JSON: " +
127              e.getMessage(), e);
128        }
129      }
130    
131      /**
132       * {@inheritDoc}
133       */
134      public SCIMException unmarshalError(final InputStream inputStream)
135          throws InvalidResourceException
136      {
137        try
138        {
139          final JSONObject jsonObject =
140              new JSONObject(new JSONTokener(inputStream));
141    
142          if(jsonObject.has("Errors"))
143          {
144            JSONArray errors = jsonObject.getJSONArray("Errors");
145            if(errors.length() >= 1)
146            {
147              JSONObject error = errors.getJSONObject(0);
148              int code = error.optInt("code");
149              String description = error.optString("description");
150              return SCIMException.createException(code, description);
151            }
152          }
153          return null;
154        }
155        catch (JSONException e)
156        {
157          throw new InvalidResourceException("Error while reading JSON: " +
158              e.getMessage(), e);
159        }
160      }
161    
162    
163    
164      /**
165       * {@inheritDoc}
166       */
167      public void bulkUnmarshal(final InputStream inputStream,
168                                final BulkConfig bulkConfig,
169                                final BulkContentHandler handler)
170          throws SCIMException
171      {
172        final JsonBulkParser bulkUnmarshaller =
173            new JsonBulkParser(inputStream, bulkConfig, handler);
174        bulkUnmarshaller.unmarshal();
175      }
176    
177    
178    
179      /**
180       * {@inheritDoc}
181       */
182      public void bulkUnmarshal(final File file,
183                                final BulkConfig bulkConfig,
184                                final BulkContentHandler handler)
185          throws SCIMException
186      {
187        // First pass: ensure the number of operations is less than the max,
188        // and save the failOnErrrors value.
189        final AtomicInteger failOnErrorsValue = new AtomicInteger(-1);
190        final BulkContentHandler preProcessHandler = new BulkContentHandler()
191        {
192          @Override
193          public void handleFailOnErrors(final int failOnErrors)
194          {
195            failOnErrorsValue.set(failOnErrors);
196          }
197        };
198        try
199        {
200          final FileInputStream fileInputStream = new FileInputStream(file);
201          try
202          {
203            final BufferedInputStream bufferedInputStream =
204                new BufferedInputStream(fileInputStream);
205            try
206            {
207              final JsonBulkParser jsonBulkParser =
208                  new JsonBulkParser(bufferedInputStream, bulkConfig,
209                                     preProcessHandler);
210              jsonBulkParser.setSkipOperations(true);
211              jsonBulkParser.unmarshal();
212            }
213            finally
214            {
215              bufferedInputStream.close();
216            }
217          }
218          finally
219          {
220            fileInputStream.close();
221          }
222        }
223        catch (IOException e)
224        {
225          Debug.debugException(e);
226          throw new ServerErrorException(
227              "Error pre-processing bulk request: " + e.getMessage());
228        }
229    
230        int failOnErrors = failOnErrorsValue.get();
231        if (failOnErrors != -1)
232        {
233          handler.handleFailOnErrors(failOnErrors);
234        }
235    
236        // Second pass: Parse fully.
237        try
238        {
239          final FileInputStream fileInputStream = new FileInputStream(file);
240          try
241          {
242            final BufferedInputStream bufferedInputStream =
243                new BufferedInputStream(fileInputStream);
244            try
245            {
246              final JsonBulkParser jsonBulkParser =
247                  new JsonBulkParser(bufferedInputStream, bulkConfig, handler);
248              jsonBulkParser.unmarshal();
249            }
250            finally
251            {
252              bufferedInputStream.close();
253            }
254          }
255          finally
256          {
257            fileInputStream.close();
258          }
259        }
260        catch (IOException e)
261        {
262          Debug.debugException(e);
263          throw new ServerErrorException(
264              "Error parsing bulk request: " + e.getMessage());
265        }
266      }
267    }