com.unboundid.scim2.common.BaseScimResource Maven / Gradle / Ivy
/*
* Copyright 2015-2020 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.scim2.common;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.unboundid.scim2.common.annotations.Schema;
import com.unboundid.scim2.common.exceptions.BadRequestException;
import com.unboundid.scim2.common.exceptions.ScimException;
import com.unboundid.scim2.common.types.Meta;
import com.unboundid.scim2.common.utils.JsonUtils;
import com.unboundid.scim2.common.utils.SchemaUtils;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* The base SCIM object. This object contains all of the
* attributes required of SCIM objects.
*
* BaseScimResource is used when the schema is known ahead of
* time. In that case a developer can derive a class from
* BaseScimResource and annotate the class. The class should
* be a Java bean. This will make it easier to work with the SCIM
* object since you will just have plain old getters and setters
* for core attributes. Extension attributes cannot be bound to
* members of the class but they can still be accessed using the
* {@link #getExtensionObjectNode} method or the {@link #getExtensionValues},
* {@link #replaceExtensionValue}, and {@link #addExtensionValue} methods.
*
* If you have a BaseScimResource derived object, you can always get a
* {@link GenericScimResource} by calling {@link #asGenericScimResource()}.
* You could also go the other way by calling
* {@link GenericScimResource#getObjectNode()}, followed by
* {@link JsonUtils#nodeToValue(JsonNode, Class)}.
*
* @see GenericScimResource
*/
@JsonPropertyOrder({ "schemas", "id", "externalId" })
public abstract class BaseScimResource
implements ScimResource
{
private String id;
private String externalId;
private Meta meta;
@JsonProperty("schemas")
private Set schemaUrns = new HashSet();
private final ObjectNode extensionObjectNode =
JsonUtils.getJsonNodeFactory().objectNode();
/**
* Constructs a new BaseScimResource object, and sets the urn if
* the class extending this one is annotated.
*/
public BaseScimResource()
{
this(null);
}
/**
* Constructs a new BaseScimResource object, and sets the urn if
* the class extending this one is annotated.
*
* @param id The ID fo the object.
*/
public BaseScimResource(final String id)
{
this.id = id;
addMyUrn();
}
/**
* Gets the ObjectNode
that contains all extension attributes.
* @return a ObjectNode
.
*/
@JsonIgnore
public ObjectNode getExtensionObjectNode()
{
return this.extensionObjectNode;
}
/**
* {@inheritDoc}
*/
public Meta getMeta()
{
return meta;
}
/**
* {@inheritDoc}
*/
public void setMeta(final Meta meta)
{
this.meta = meta;
}
/**
* {@inheritDoc}
*/
public String getId()
{
return id;
}
/**
* {@inheritDoc}
*/
public void setId(final String id)
{
this.id = id;
}
/**
* {@inheritDoc}
*/
public String getExternalId()
{
return externalId;
}
/**
* {@inheritDoc}
*/
public void setExternalId(final String externalId)
{
this.externalId = externalId;
}
/**
* {@inheritDoc}
*/
public Set getSchemaUrns()
{
if(schemaUrns == null)
{
schemaUrns = new HashSet();
}
return schemaUrns;
}
/**
* {@inheritDoc}
*/
public void setSchemaUrns(final Collection schemaUrns)
{
this.schemaUrns = new HashSet(schemaUrns);
}
/**
* This method is used during json deserialization. It will be called
* in the event that a value is given for an field that is not defined
* in the class.
*
* @param key name of the field.
* @param value value of the field.
*
* @throws ScimException if the key is not an extension attribute namespace
* (the key name doesn't start with "{@code urn:}").
*/
@JsonAnySetter
protected void setAny(final String key,
final JsonNode value)
throws ScimException
{
if(SchemaUtils.isUrn(key) && value.isObject())
{
extensionObjectNode.set(key, value);
}
else
{
String message = "Core attribute " + key + " is undefined";
Schema schemaAnnotation = this.getClass().getAnnotation(Schema.class);
if(schemaAnnotation != null)
{
message += " for schema " + schemaAnnotation.id();
}
throw BadRequestException.invalidSyntax(message);
}
}
/**
* Used to get values that were deserialized from json where there was
* no matching field in the class.
* @return the value of the field.
*/
@JsonAnyGetter
protected Map getAny()
{
HashMap map =
new HashMap(extensionObjectNode.size());
Iterator> i = extensionObjectNode.fields();
while(i.hasNext())
{
Map.Entry field = i.next();
map.put(field.getKey(), field.getValue());
}
return map;
}
/**
* Adds the urn of this class to the list of schemas for this object.
* This is taken from the schema annotation of a class that extends
* this class. If the class has no schema annotation, no schema urn
* will be added.
*/
private void addMyUrn()
{
String mySchema = SchemaUtils.getSchemaUrn(this.getClass());
if((mySchema != null) && (!mySchema.isEmpty()))
{
getSchemaUrns().add(mySchema);
}
}
/**
* Retrieve all JSON nodes of the extension attribute referenced by the
* provided path. Equivalent to using the
* {@link JsonUtils#findMatchingPaths(Path, ObjectNode)}
* method: JsonUtils.getValues(Path.fromString(path),
* getExtensionObjectNode()).
*
* The {@link JsonUtils#nodeToValue(JsonNode, Class)} method may be used to
* bind the retrieved JSON node into specific value type instances.
*
* @param path The path to the attribute whose value to retrieve.
*
* @return List of all JSON nodes referenced by the provided path.
* @throws ScimException If the path is invalid.
*/
public List getExtensionValues(final String path)
throws ScimException
{
return getExtensionValues(Path.fromString(path));
}
/**
* Retrieve all JSON nodes of the extension attribute referenced by the
* provided path. Equivalent to using the
* {@link JsonUtils#findMatchingPaths(Path, ObjectNode)}
* method: JsonUtils.getValues(path, getExtensionObjectNode()).
*
* The {@link JsonUtils#nodeToValue(JsonNode, Class)} method may be used to
* bind the retrieved JSON node into specific value type instances.
*
* @param path The path to the attribute whose value to retrieve.
*
* @return List of all JSON nodes referenced by the provided path.
* @throws ScimException If the path is invalid.
*/
public List getExtensionValues(final Path path)
throws ScimException
{
return JsonUtils.findMatchingPaths(path, extensionObjectNode);
}
/**
* Update the value of the extension attribute at the provided path.
* Equivalent to using the {@link JsonUtils#replaceValue(Path, ObjectNode,
* JsonNode)} method: JsonUtils.replaceValues(Path.fromString(path),
* getExtensionObjectNode(), value).
*
* The {@link JsonUtils#valueToNode(Object)} method may be used to convert
* the given value instance to a JSON node.
*
* @param path The path to the attribute whose value to set.
* @param value The value(s) to set.
* @throws ScimException If the path is invalid.
*/
public void replaceExtensionValue(final String path, final JsonNode value)
throws ScimException
{
replaceExtensionValue(Path.fromString(path), value);
}
/**
* Update the value of the extension attribute at the provided path.
* Equivalent to using the {@link JsonUtils#replaceValue(Path, ObjectNode,
* JsonNode)} method: JsonUtils.replaceValues(path, getExtensionObjectNode(),
* value).
*
* The {@link JsonUtils#valueToNode(Object)} method may be used to convert
* the given value instance to a JSON node.
*
* @param path The path to the attribute whose value to set.
* @param value The value(s) to set.
* @throws ScimException If the path is invalid.
*/
public void replaceExtensionValue(final Path path, final JsonNode value)
throws ScimException
{
JsonUtils.replaceValue(path, extensionObjectNode, value);
}
/**
* Retrieve a SCIM extension based on the annotations of the class
* provided. The returned value will be converted to a POJO of the
* type specified.
*
* @param clazz The class used to determine the type of the object returned
* and the schema of the extension.
* @param the type of object to return.
*
* @return The matching extension object, or {@code null} if no extension of
* that type exists.
*/
@JsonIgnore
public T getExtension(final Class clazz)
{
try
{
JsonNode extensionNode =
extensionObjectNode.path(getSchemaUrnOrThrowException(clazz));
if(extensionNode.isMissingNode())
{
return null;
}
else
{
return JsonUtils.nodeToValue(extensionNode, clazz);
}
}
catch(JsonProcessingException ex)
{
throw new RuntimeException(ex);
}
}
/**
* Sets a SCIM extension to the given value based on the annotations
* of the class provided. The value will be set for an extension named
* based on the annotations of the class supplied.
*
* @param extension The value to set. This also is used to determine what
* the extension's urn is.
* @param the type of object.
*/
@JsonIgnore
public void setExtension(final T extension)
{
String schemaUrn = getSchemaUrnOrThrowException(extension.getClass());
extensionObjectNode.set(schemaUrn, JsonUtils.valueToNode(extension));
schemaUrns.add(schemaUrn);
}
/**
* Removes a SCIM extension. The extension urn is based on the annotations
* of the class provided.
*
* @param clazz the class used to determine the schema urn.
* @param the type of the class object.
*
* @return true if the extension was removed, or false if the extension
* was not present.
*/
public boolean removeExtension(final Class clazz)
{
String schemaUrn = getSchemaUrnOrThrowException(clazz);
if(extensionObjectNode.remove(schemaUrn) == null)
{
return false;
}
else
{
schemaUrns.remove(schemaUrn);
return true;
}
}
private String getSchemaUrnOrThrowException(final Class clazz)
{
String schemaUrn = SchemaUtils.getSchemaUrn(clazz);
if(schemaUrn == null)
{
throw new IllegalArgumentException(
"Unable to determine the extension class schema.");
}
return schemaUrn;
}
/**
* Add new values for the extension attribute at the provided path. Equivalent
* to using the {@link JsonUtils#addValue(Path, ObjectNode, JsonNode)} method:
* JsonUtils.addValue(Path.fromString(path), getExtensionObjectNode(),
* values).
*
* The {@link JsonUtils#valueToNode(Object)} method may be used to convert
* the given value instance to a JSON node.
*
* @param path The path to the attribute whose values to add.
* @param values The value(s) to add.
* @throws ScimException If the path is invalid.
*/
public void addExtensionValue(final String path, final ArrayNode values)
throws ScimException
{
addExtensionValue(Path.fromString(path), values);
}
/**
* Add new values to the extension attribute at the provided path. Equivalent
* to using the {@link JsonUtils#addValue(Path, ObjectNode, JsonNode)} method:
* JsonUtils.addValue(path, getObjectNode(), values).
*
* The {@link JsonUtils#valueToNode(Object)} method may be used to convert
* the given value instance to a JSON node.
*
* @param path The path to the attribute whose values to add.
* @param values The value(s) to add.
* @throws ScimException If the path is invalid.
*/
public void addExtensionValue(final Path path, final ArrayNode values)
throws ScimException
{
JsonUtils.addValue(path, extensionObjectNode, values);
}
/**
* Removes values of the extension attribute at the provided path. Equivalent
* to using the {@link JsonUtils#removeValues(Path, ObjectNode)} method:
* JsonUtils.removeValue(Path.fromString(path), getObjectNode(), values).
*
* @param path The path to the attribute whose values to remove.
* @return Whether one or more values where removed.
* @throws ScimException If the path is invalid.
*/
public boolean removeExtensionValues(final String path)
throws ScimException
{
return removeExtensionValues(Path.fromString(path));
}
/**
* Removes values of the extension attribute at the provided path. Equivalent
* to using the {@link JsonUtils#removeValues(Path, ObjectNode)} method:
* JsonUtils.removeValue(Path.fromString(path), getObjectNode(), values).
*
* @param path The path to the attribute whose values to remove.
* @return Whether one or more values where removed.
* @throws ScimException If the path is invalid.
*/
public boolean removeExtensionValues(final Path path)
throws ScimException
{
List nodes = JsonUtils.removeValues(path, extensionObjectNode);
return !nodes.isEmpty();
}
/**
* {@inheritDoc}
*/
public GenericScimResource asGenericScimResource()
{
ObjectNode object =
JsonUtils.valueToNode(this);
return new GenericScimResource(object);
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(final Object o)
{
if (this == o)
{
return true;
}
if (o == null || getClass() != o.getClass())
{
return false;
}
BaseScimResource that = (BaseScimResource) o;
if (extensionObjectNode != null ?
!extensionObjectNode.equals(that.extensionObjectNode) :
that.extensionObjectNode != null)
{
return false;
}
if (externalId != null ? !externalId.equals(that.externalId) :
that.externalId != null)
{
return false;
}
if (id != null ? !id.equals(that.id) : that.id != null)
{
return false;
}
if (meta != null ? !meta.equals(that.meta) : that.meta != null)
{
return false;
}
if (schemaUrns != null ? !schemaUrns.equals(that.schemaUrns) :
that.schemaUrns != null)
{
return false;
}
return true;
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode()
{
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (externalId != null ? externalId.hashCode() : 0);
result = 31 * result + (meta != null ? meta.hashCode() : 0);
result = 31 * result + (schemaUrns != null ? schemaUrns.hashCode() : 0);
result = 31 * result + (extensionObjectNode != null ?
extensionObjectNode.hashCode() : 0);
return result;
}
/**
* {@inheritDoc}
*/
@Override
public String toString()
{
try
{
return JsonUtils.getObjectWriter().withDefaultPrettyPrinter().
writeValueAsString(this);
}
catch (JsonProcessingException e)
{
throw new RuntimeException(e);
}
}
}