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}