001 /*
002 * Copyright 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.ResourceFactory;
022 import com.unboundid.scim.schema.AttributeDescriptor;
023 import com.unboundid.scim.schema.ResourceDescriptor;
024 import com.unboundid.scim.sdk.InvalidResourceException;
025 import com.unboundid.scim.sdk.SCIMAttribute;
026 import com.unboundid.scim.sdk.SCIMAttributeValue;
027 import com.unboundid.scim.sdk.SCIMConstants;
028 import com.unboundid.scim.sdk.SCIMObject;
029 import org.json.JSONArray;
030 import org.json.JSONException;
031 import org.json.JSONObject;
032
033 import java.util.ArrayList;
034 import java.util.Iterator;
035 import java.util.List;
036
037
038
039 /**
040 * Helper class for JSON unmarshalling.
041 */
042 public class JsonParser
043 {
044 /**
045 * Read a SCIM resource from the specified JSON object.
046 *
047 * @param <R> The type of resource instance.
048 * @param jsonObject The JSON object to be read.
049 * @param resourceDescriptor The descriptor of the SCIM resource to be read.
050 * @param resourceFactory The resource factory to use to create the resource
051 * instance.
052 * @param defaultSchemas The set of schemas used by attributes of the
053 * resource, or {@code null} if the schemas must be
054 * provided in the resource object.
055 *
056 * @return The SCIM resource that was read.
057 *
058 * @throws JSONException If an error occurred.
059 * @throws InvalidResourceException if a schema error occurs.
060 */
061 protected <R extends BaseResource> R unmarshal(
062 final JSONObject jsonObject,
063 final ResourceDescriptor resourceDescriptor,
064 final ResourceFactory<R> resourceFactory,
065 final JSONArray defaultSchemas)
066 throws JSONException, InvalidResourceException
067 {
068 try
069 {
070 final SCIMObject scimObject = new SCIMObject();
071
072 // The first keyed object ought to be a schemas array, but it may not be
073 // present if 1) the attrs are all core and 2) the client decided to omit
074 // the schema declaration.
075 final JSONArray schemas;
076 if (jsonObject.has("schemas"))
077 {
078 schemas = jsonObject.getJSONArray("schemas");
079 }
080 else
081 {
082 schemas = defaultSchemas;
083 }
084
085 // Read the core attributes.
086 for (AttributeDescriptor attributeDescriptor : resourceDescriptor
087 .getAttributes())
088 {
089 final String externalAttributeName = attributeDescriptor.getName();
090 final Object jsonAttribute = jsonObject.opt(externalAttributeName);
091 if (jsonAttribute != null)
092 {
093 scimObject.addAttribute(
094 create(attributeDescriptor, jsonAttribute));
095 }
096 }
097
098 // Read the extension attributes.
099 if (schemas != null)
100 {
101 for (int i = 0; i < schemas.length(); i++)
102 {
103 final String schema = schemas.getString(i);
104 if (schema.equalsIgnoreCase(SCIMConstants.SCHEMA_URI_CORE))
105 {
106 continue;
107 }
108
109 final JSONObject schemaAttrs = jsonObject.optJSONObject(schema);
110 if (schemaAttrs != null)
111 {
112 if (resourceDescriptor.getAttributeSchemas().contains(schema))
113 {
114 final Iterator keys = schemaAttrs.keys();
115 while (keys.hasNext())
116 {
117 final String attributeName = (String) keys.next();
118 final AttributeDescriptor attributeDescriptor =
119 resourceDescriptor.getAttribute(schema, attributeName);
120 final Object jsonAttribute = schemaAttrs.get(attributeName);
121 scimObject.addAttribute(
122 create(attributeDescriptor, jsonAttribute));
123 }
124 }
125 }
126 }
127 }
128
129 return resourceFactory.createResource(resourceDescriptor, scimObject);
130 }
131 catch (Exception e)
132 {
133 throw new InvalidResourceException(
134 "Resource '" + resourceDescriptor.getName() + "' is malformed: " +
135 e.getMessage());
136 }
137 }
138
139
140
141 /**
142 * Parse a simple attribute from its representation as a JSON Object.
143 *
144 * @param jsonAttribute The JSON object representing the attribute.
145 * @param attributeDescriptor The attribute descriptor.
146 *
147 * @return The parsed attribute.
148 */
149 protected SCIMAttribute createSimpleAttribute(
150 final Object jsonAttribute,
151 final AttributeDescriptor attributeDescriptor)
152 {
153 return SCIMAttribute.create(attributeDescriptor,
154 SCIMAttributeValue.createValue(attributeDescriptor.getDataType(),
155 jsonAttribute.toString()));
156 }
157
158
159
160 /**
161 * Parse a multi-valued attribute from its representation as a JSON Object.
162 *
163 * @param jsonAttribute The JSON object representing the attribute.
164 * @param attributeDescriptor The attribute descriptor.
165 *
166 * @return The parsed attribute.
167 *
168 * @throws JSONException Thrown if error creating multi-valued attribute.
169 * @throws InvalidResourceException if a schema error occurs.
170 */
171 protected SCIMAttribute createMutiValuedAttribute(
172 final JSONArray jsonAttribute,
173 final AttributeDescriptor attributeDescriptor)
174 throws JSONException, InvalidResourceException
175 {
176 final List<SCIMAttributeValue> values =
177 new ArrayList<SCIMAttributeValue>(jsonAttribute.length());
178
179 for (int i = 0; i < jsonAttribute.length(); i++)
180 {
181 Object o = jsonAttribute.get(i);
182 SCIMAttributeValue value;
183 if(o instanceof JSONObject)
184 {
185 value = createComplexAttribute((JSONObject) o, attributeDescriptor);
186 }
187 else
188 {
189 SCIMAttribute subAttr = SCIMAttribute.create(
190 attributeDescriptor.getSubAttribute("value"),
191 SCIMAttributeValue.createValue(attributeDescriptor.getDataType(),
192 o.toString()));
193 value = SCIMAttributeValue.createComplexValue(subAttr);
194 }
195 values.add(value);
196 }
197 SCIMAttributeValue[] vals =
198 new SCIMAttributeValue[values.size()];
199 vals = values.toArray(vals);
200 return SCIMAttribute.create(attributeDescriptor, vals);
201 }
202
203
204
205 /**
206 * Parse a complex attribute from its representation as a JSON Object.
207 *
208 * @param jsonAttribute The JSON object representing the attribute.
209 * @param attributeDescriptor The attribute descriptor.
210 *
211 * @return The parsed attribute.
212 *
213 * @throws org.json.JSONException Thrown if error creating complex attribute.
214 * @throws InvalidResourceException if a schema error occurs.
215 */
216 protected SCIMAttributeValue createComplexAttribute(
217 final JSONObject jsonAttribute,
218 final AttributeDescriptor attributeDescriptor)
219 throws JSONException, InvalidResourceException
220 {
221 final Iterator keys = jsonAttribute.keys();
222 final List<SCIMAttribute> complexAttrs =
223 new ArrayList<SCIMAttribute>(jsonAttribute.length());
224 while (keys.hasNext())
225 {
226 final String key = (String) keys.next();
227 final AttributeDescriptor subAttribute =
228 attributeDescriptor.getSubAttribute(key);
229 if (subAttribute != null)
230 {
231 SCIMAttribute childAttr;
232 // Allow multi-valued sub-attribute as the resource schema needs this.
233 if (subAttribute.isMultiValued())
234 {
235 final JSONArray o = jsonAttribute.getJSONArray(key);
236 childAttr = createMutiValuedAttribute(o, subAttribute);
237 }
238 else
239 {
240 final Object o = jsonAttribute.get(key);
241 childAttr = createSimpleAttribute(o, subAttribute);
242 }
243 complexAttrs.add(childAttr);
244 }
245 }
246
247 return SCIMAttributeValue.createComplexValue(complexAttrs);
248 }
249
250
251
252 /**
253 * Create a SCIM attribute from its JSON object representation.
254 *
255 * @param descriptor The attribute descriptor.
256 * @param jsonAttribute The JSON object representing the attribute.
257 *
258 * @return The created SCIM attribute.
259 *
260 * @throws JSONException If the JSON object is not valid.
261 * @throws InvalidResourceException If a schema error occurs.
262 */
263 protected SCIMAttribute create(
264 final AttributeDescriptor descriptor, final Object jsonAttribute)
265 throws JSONException, InvalidResourceException
266 {
267 if (descriptor.isMultiValued())
268 {
269 if (!(jsonAttribute instanceof JSONArray))
270 {
271 throw new InvalidResourceException(
272 "JSON array expected for multi-valued attribute '" +
273 descriptor.getName() + "'");
274 }
275 return createMutiValuedAttribute((JSONArray) jsonAttribute, descriptor);
276 }
277 else if (descriptor.getDataType() == AttributeDescriptor.DataType.COMPLEX)
278 {
279 if (!(jsonAttribute instanceof JSONObject))
280 {
281 throw new InvalidResourceException(
282 "JSON object expected for multi-valued attribute '" +
283 descriptor.getName() + "'");
284 }
285 return SCIMAttribute.create(
286 descriptor,
287 createComplexAttribute((JSONObject) jsonAttribute, descriptor));
288 }
289 else
290 {
291 return this.createSimpleAttribute(jsonAttribute, descriptor);
292 }
293 }
294 }