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