com.unboundid.scim.marshal.json.JsonParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scim-sdk Show documentation
Show all versions of scim-sdk Show documentation
The UnboundID SCIM SDK is a library that may be used to interact with various
types of SCIM-enabled endpoints (such as the UnboundID server products) to
perform lightweight, cloud-based identity management via the SCIM Protocol.
See http://www.simplecloud.info for more information.
/*
* Copyright 2012-2019 Ping Identity Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License (GPLv2 only)
* or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
package com.unboundid.scim.marshal.json;
import com.unboundid.scim.data.BaseResource;
import com.unboundid.scim.data.ResourceFactory;
import com.unboundid.scim.schema.AttributeDescriptor;
import com.unboundid.scim.schema.CoreSchema;
import com.unboundid.scim.schema.ResourceDescriptor;
import com.unboundid.scim.sdk.InvalidResourceException;
import com.unboundid.scim.sdk.SCIMAttribute;
import com.unboundid.scim.sdk.SCIMAttributeValue;
import com.unboundid.scim.sdk.SCIMConstants;
import com.unboundid.scim.sdk.SCIMObject;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.unboundid.scim.sdk.StaticUtils.toLowerCase;
/**
* Helper class for JSON unmarshalling.
*/
public class JsonParser
{
/**
* Read a SCIM resource from the specified JSON object.
*
* @param The type of resource instance.
* @param jsonObject The JSON object to be read.
* @param resourceDescriptor The descriptor of the SCIM resource to be read.
* @param resourceFactory The resource factory to use to create the resource
* instance.
* @param defaultSchemas The set of schemas used by attributes of the
* resource, or {@code null} if the schemas must be
* provided in the resource object.
*
* @return The SCIM resource that was read.
*
* @throws JSONException If an error occurred.
* @throws InvalidResourceException if a schema error occurs.
*/
protected R unmarshal(
final JSONObject jsonObject,
final ResourceDescriptor resourceDescriptor,
final ResourceFactory resourceFactory,
final JSONArray defaultSchemas)
throws JSONException, InvalidResourceException
{
try
{
final SCIMObject scimObject = new SCIMObject();
final boolean implicitSchemaChecking = Boolean.getBoolean(
SCIMConstants.IMPLICIT_SCHEMA_CHECKING_PROPERTY);
// The first keyed object ought to be a schemas array, but it may not be
// present if 1) the attrs are all core and 2) the client decided to omit
// the schema declaration.
final JSONArray schemas;
if (jsonObject.has(SCIMConstants.SCHEMAS_ATTRIBUTE_NAME))
{
schemas = jsonObject.getJSONArray(SCIMConstants.SCHEMAS_ATTRIBUTE_NAME);
}
else if (defaultSchemas != null)
{
schemas = defaultSchemas;
}
else
{
String[] schemaArray = new String[1];
schemaArray[0] = resourceDescriptor.getSchema();
schemas = new JSONArray(schemaArray);
}
final Set schemaSet = new HashSet(schemas.length());
if (implicitSchemaChecking)
{
schemaSet.addAll(resourceDescriptor.getAttributeSchemas());
}
for (int i = 0; i < schemas.length(); i++)
{
schemaSet.add(toLowerCase(schemas.getString(i)));
}
final Iterator k = jsonObject.keys();
while (k.hasNext())
{
final String attributeKey = (String) k.next();
final String attributeKeyLower = toLowerCase(attributeKey);
if(SCIMConstants.SCHEMAS_ATTRIBUTE_NAME.equals(attributeKeyLower))
{
continue;
}
if (schemaSet.contains(attributeKeyLower))
{
//This key is a container for some extended schema
JSONObject schemaAttrs = jsonObject.getJSONObject(attributeKey);
final Iterator keys = schemaAttrs.keys();
while (keys.hasNext())
{
final String attributeName = (String) keys.next();
final AttributeDescriptor attributeDescriptor =
resourceDescriptor.getAttribute(attributeKey, attributeName);
final Object jsonAttribute = schemaAttrs.get(attributeName);
final SCIMAttribute a = create(attributeDescriptor, jsonAttribute);
if (a != null)
{
scimObject.addAttribute(a);
}
}
}
else
{
final Object jsonAttribute = jsonObject.get(attributeKey);
if (implicitSchemaChecking)
{
//Try to determine the schema for this attribute
final String attributeName = attributeKey;
final String schema =
resourceDescriptor.findAttributeSchema(attributeName);
final AttributeDescriptor attributeDescriptor =
resourceDescriptor.getAttribute(schema, attributeName);
if (CoreSchema.META_DESCRIPTOR.equals(attributeDescriptor))
{
try
{
// Special implicit schema processing for meta.attributes
// which contains the names of the attributes to remove from
// the Resource during a PATCH operation. These each should be
// fully qualified with schema urn by the client, but if they
// are not we can try to determine the schema here.
JSONObject jsonMetaObj = ((JSONObject)jsonAttribute);
JSONArray metaAttrs = null;
final Iterator keys = jsonMetaObj.keys();
while (keys.hasNext())
{
final String key = (String) keys.next();
if ("attributes".equals(key.toLowerCase()))
{
Object attrObj = jsonMetaObj.get(key);
if (attrObj instanceof JSONArray)
{
metaAttrs = (JSONArray) attrObj;
}
break;
}
}
if (metaAttrs != null)
{
JSONArray newMetaAttrs = new JSONArray();
for (int i=0; i < metaAttrs.length(); i++)
{
String metaAttr = (String) metaAttrs.get(i);
String metaSchema = resourceDescriptor.findAttributeSchema(
metaAttr);
// The schema returned will be null if attribute value was
// already fully qualified.
if (metaSchema != null)
{
metaAttr = metaSchema +
SCIMConstants.SEPARATOR_CHAR_QUALIFIED_ATTRIBUTE +
metaAttr;
}
newMetaAttrs.put(metaAttr);
}
jsonMetaObj.put("attributes", newMetaAttrs);
}
}
catch (Exception ignore)
{
// Don't fail because of implicit schema checking
}
}
final SCIMAttribute a = create(attributeDescriptor, jsonAttribute);
if (a != null)
{
scimObject.addAttribute(a);
}
}
else
{
if (!schemaSet.contains(SCIMConstants.SCHEMA_URI_CORE))
{
throw new Exception("'" + SCIMConstants.SCHEMA_URI_CORE +
"' must be declared in the schemas attribute.");
}
final AttributeDescriptor attributeDescriptor =
resourceDescriptor.getAttribute(
SCIMConstants.SCHEMA_URI_CORE,
attributeKey);
final SCIMAttribute a = create(attributeDescriptor, jsonAttribute);
if (a != null)
{
scimObject.addAttribute(a);
}
}
}
}
return resourceFactory.createResource(resourceDescriptor, scimObject);
}
catch (Exception e)
{
throw new InvalidResourceException(
"Resource '" + resourceDescriptor.getName() + "' is malformed: " +
e.getMessage(), e);
}
}
/**
* Parse a simple attribute from its representation as a JSON Object.
*
* @param jsonAttribute The JSON object representing the attribute.
* @param attributeDescriptor The attribute descriptor.
*
* @return The parsed attribute.
*/
protected SCIMAttribute createSimpleAttribute(
final Object jsonAttribute,
final AttributeDescriptor attributeDescriptor)
{
return SCIMAttribute.create(attributeDescriptor,
SCIMAttributeValue.createValue(attributeDescriptor.getDataType(),
jsonAttribute.toString()));
}
/**
* Parse a multi-valued attribute from its representation as a JSON Array.
*
* @param jsonAttribute The JSON array representing the attribute.
* @param attributeDescriptor The attribute descriptor.
*
* @return The parsed attribute, or {@code null} if there are no non-null
* values in the array.
*
* @throws JSONException Thrown if error creating multi-valued attribute.
* @throws InvalidResourceException if a schema error occurs.
*/
protected SCIMAttribute createMultiValuedAttribute(
final JSONArray jsonAttribute,
final AttributeDescriptor attributeDescriptor)
throws JSONException, InvalidResourceException
{
final List values =
new ArrayList(jsonAttribute.length());
for (int i = 0; i < jsonAttribute.length(); i++)
{
Object o = jsonAttribute.get(i);
if (o.equals(JSONObject.NULL))
{
continue;
}
SCIMAttributeValue value;
if(o instanceof JSONObject)
{
value = createComplexAttribute((JSONObject) o, attributeDescriptor);
}
else
{
SCIMAttribute subAttr = SCIMAttribute.create(
attributeDescriptor.getSubAttribute("value"),
SCIMAttributeValue.createValue(attributeDescriptor.getDataType(),
o.toString()));
value = SCIMAttributeValue.createComplexValue(subAttr);
}
values.add(value);
}
if (values.isEmpty())
{
return null;
}
SCIMAttributeValue[] vals =
new SCIMAttributeValue[values.size()];
vals = values.toArray(vals);
return SCIMAttribute.create(attributeDescriptor, vals);
}
/**
* Parse a complex attribute from its representation as a JSON Object.
*
* @param jsonAttribute The JSON object representing the attribute.
* @param attributeDescriptor The attribute descriptor.
*
* @return The parsed attribute.
*
* @throws org.json.JSONException Thrown if error creating complex attribute.
* @throws InvalidResourceException if a schema error occurs.
*/
protected SCIMAttributeValue createComplexAttribute(
final JSONObject jsonAttribute,
final AttributeDescriptor attributeDescriptor)
throws JSONException, InvalidResourceException
{
final Iterator keys = jsonAttribute.keys();
final List complexAttrs =
new ArrayList(jsonAttribute.length());
while (keys.hasNext())
{
final String key = (String) keys.next();
final AttributeDescriptor subAttribute =
attributeDescriptor.getSubAttribute(key);
if (subAttribute != null)
{
SCIMAttribute childAttr = null;
// Allow multi-valued sub-attribute as the resource schema needs this.
if (subAttribute.isMultiValued())
{
final JSONArray o = jsonAttribute.getJSONArray(key);
childAttr = createMultiValuedAttribute(o, subAttribute);
}
else
{
final Object o = jsonAttribute.get(key);
if (!o.equals(JSONObject.NULL))
{
childAttr = createSimpleAttribute(o, subAttribute);
}
}
if (childAttr != null)
{
complexAttrs.add(childAttr);
}
}
}
return SCIMAttributeValue.createComplexValue(complexAttrs);
}
/**
* Create a SCIM attribute from its JSON object representation.
*
* @param descriptor The attribute descriptor.
* @param jsonAttribute The JSON object representing the attribute.
*
* @return The created SCIM attribute, or {@code null} if the value is null.
*
* @throws JSONException If the JSON object is not valid.
* @throws InvalidResourceException If a schema error occurs.
*/
protected SCIMAttribute create(
final AttributeDescriptor descriptor, final Object jsonAttribute)
throws JSONException, InvalidResourceException
{
if (jsonAttribute.equals(JSONObject.NULL))
{
return null;
}
if (descriptor.isMultiValued())
{
JSONArray jsonArray;
if (jsonAttribute instanceof JSONArray)
{
jsonArray = (JSONArray) jsonAttribute;
}
else
{
String[] s = new String[1];
s[0] = jsonAttribute.toString();
jsonArray = new JSONArray(s);
}
return createMultiValuedAttribute(jsonArray, descriptor);
}
else if (descriptor.getDataType() == AttributeDescriptor.DataType.COMPLEX)
{
if (!(jsonAttribute instanceof JSONObject))
{
throw new InvalidResourceException(
"JSON object expected for complex attribute '" +
descriptor.getName() + "'");
}
return SCIMAttribute.create(
descriptor,
createComplexAttribute((JSONObject) jsonAttribute, descriptor));
}
else
{
return this.createSimpleAttribute(jsonAttribute, descriptor);
}
}
/**
* Returns a copy of the specified JSONObject with all the keys lower-cased.
* This makes it much easier to use methods like JSONObject.opt() to find a
* key when you don't know what the case is.
*
* @param jsonObject the original JSON object.
* @return a new JSONObject with the keys all lower-cased.
* @throws JSONException if there is an error creating the new JSONObject.
*/
static final JSONObject makeCaseInsensitive(final JSONObject jsonObject)
throws JSONException
{
if (jsonObject == null)
{
return null;
}
Iterator keys = jsonObject.keys();
Map lowerCaseMap = new HashMap(jsonObject.length());
while (keys.hasNext())
{
String key = keys.next().toString();
String lowerCaseKey = toLowerCase(key);
lowerCaseMap.put(lowerCaseKey, jsonObject.get(key));
}
return new JSONObject(lowerCaseMap);
}
}