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