Skip to content

Working with SCIM resources

Jacob C edited this page May 19, 2016 · 6 revisions

When you make a SCIM request with ScimService, you specify the type of the object to return, which must be an implementation of the ScimResource interface. The SCIM 2 SDK provides two options from which you can choose depending on your preferences and requirements.

Using POJOs: BaseScimResource

If you understand in advance the resource types that you will be working with, then it can be a good idea to model those resource types as POJOs derived from BaseScimResource. This allows you to work with your SCIM resources using familiar Java constructs such as Strings and Collections without having to understand the JSON structure of the resource.

A SCIM POJO should implement BaseScimResource and be serializable and deserializable by Jackson. Supporting Jackson typically means following bean conventions such as providing a default no-argument constructor and getters and setters named according to a certain pattern; it could also involve using Jackson annotations such as @JsonProperty to indicate how the object should be serialized.

Core schema attributes should be represented as fields of the class. In general, SCIM types map to Java types in obvious ways. Note that you will typically need to represent a complex attribute using a custom class.

Extension schemas are represented as distinct classes, with fields representing the extension schema attributes.

The best way to see how to write your own POJO derived from BaseScimResource is to look at the UserResource class in the com.unboundid.scim2.common.types package. It implements the "User" schema described by RFC 7643.

The following example is an excerpt from the UserResource source with new comments added to illustrate some basic points.

// The @Schema annotation describes the resource type, 
// including the schema ID, importantly. This information may 
// be used by other classes in the SCIM 2 SDK.
@Schema(id="urn:ietf:params:scim:schemas:core:2.0:User",
    name="User", description = "User Account")
public class UserResource extends BaseScimResource
{
  // The @Attribute annotation is used to describe each attribute.
  @Attribute(description = "Unique identifier for the User typically " +
      "used by the user to directly authenticate to the service provider.",
      isRequired = true,
      isCaseExact = false,
      mutability = AttributeDefinition.Mutability.READ_WRITE,
      returned = AttributeDefinition.Returned.DEFAULT,
      uniqueness = AttributeDefinition.Uniqueness.SERVER)
  private String userName;

  @Attribute(description = "The components of the user's real name.",
      isRequired = false,
      mutability = AttributeDefinition.Mutability.READ_WRITE,
      returned = AttributeDefinition.Returned.DEFAULT,
      uniqueness = AttributeDefinition.Uniqueness.NONE)
  // Note that "name" is a complex attribute, so it is
  // represented by the Name class.
  private Name name;

  // A default no-argument constructor is implied.

  // Basic getters and setters are provided.
  public String getUserName()
  {
    return userName;
  }

  // Setters return this instance so that they may be chained
  // from the constructor.
  public UserResource setUserName(final String userName)
  {
    this.userName = userName;
    return this;
  }

  public Name getName()
  {
    return name;
  }

  public UserResource setName(final Name name)
  {
    this.name = name;
    return this;
  }
}

Obtaining extension attributes

A caller needing to retrieve extension schema attributes should call getExtensionValues(Path). This method returns a List<JsonNode> object, which can then be converted to the appropriate extension schema class using JsonUtils.nodeToValue(JsonNode, Class). It's a good idea to provide a convenience method in your POJO so that your callers don't need to do this themselves.

// Get all attributes for a particular extension schema
// (Note that this example omits a null check)
EnterpriseUserExtension enterpriseUserExtension =
    JsonUtils.nodeToValue(userResource.getExtensionValues(
        Path.root(EnterpriseUserExtension.class)).get(0),
        EnterpriseUserExtension.class);
// Get an attribute
String originalDepartment = enterpriseExtension.getDepartment();

// Update an extension attribute
enterpriseUserExtension.setDepartment("R&D");
scimUser.replaceExtensionValue(
  Path.root(EnterpriseUserExtension.class),
  JsonUtils.valueToNode(enterpriseUserExtension)
);
scimClient.replace(scimUser);

It may help to understand that BaseScimResource has a setter method annotated with @JsonAnySetter; this populates the data structure that is manipulated by getExtensionValues(Path) and replaceExtensionValue(…). This setter collects any attributes that aren't defined as fields of the POJO as long as they are part of an extension schema. This means that you aren't actually required to provide POJOs corresponding to extension schemas if you don't want to; if you're comfortable working with JsonNodes, then you can call getExtensionValues(Path) and work with the return value without converting it.

Using Jackson: GenericScimResource

If you cannot provide classes representing your SCIM resource types in advance, then you can work with a resources using GenericScimResource, a concrete implementation of ScimResource that exposes attributes as Jackson JSON nodes. It is highly recommended that you familiarize yourself with the Jackson tree API before working with GenericScimResource. The Jackson documentation provides good introductory information.

Most methods in GenericScimResource are getters or setters for resource attributes, and all of them take at least one argument, a Path object. Paths are used to target an attribute in the resource, such as "userName" or "emails". Be warned that using paths requires knowledge of the SCIM resource's JSON structure and an understanding of the type of JsonNode that will be returned; paths may target leaf nodes or container nodes, which may produce unexpected behavior. See Working with SCIM Paths.

Setter methods take a value to set, which will either be an object representing a SCIM primitive type (e.g., String, Boolean, Date, URI, etc.) or a Jackson JsonNode. If the value that you have is some other type of object, as long as it's serializable, you should be able to convert it to a JsonNode using JsonUtils.valueToNode(…).

Accessor methods often return an instance of JsonNode. To convert the JsonNode to some other object type, you can call JsonUtils.nodeToValue(…).

Converting between BaseScimResources and GenericScimResources

If you have an object derived from BaseScimResource, you can always convert it into an instance of GenericScimResource, and vice versa.

ScimService scimClient = …
UserResource userResource =
  scimClient.retrieve(ScimService.ME_URI, UserResource.class);
GenericScimResource gsr = userResource.asGenericScimResource();
gsr.replaceValue("userName", "newUsername");
ObjectNode objectNode = gsr.getObjectNode();
userResource = JsonUtils.nodeToValue(objectNode, UserResource.class);

Choosing between BaseScimResource and GenericScimResource

It's likely that in most cases, it will be worth the trouble to create model classes for your SCIM resource types that are derived from BaseScimResource. POJOs tend to be comprised of familiar Java types and constructs such as Strings and Collections (or other POJOs); they're therefore easy to use. GenericScimResource offers a tremendous amount of flexibility, but may be too advanced for everyday use by some developers. To summarize earlier points:

You have this use case Then use this class
You know the SCIM schema in advance, and it is unlikely to change often. A custom POJO derived from BaseScimResource
You have no need to manipulate SCIM resources as JSON objects. A custom POJO derived from BaseScimResource
You do not know the SCIM schema in advance, or it is subject to frequent change. GenericScimResource
You prefer to work with a Jackson-based API. GenericScimResource
You are implementing a SCIM service provider that supports extensible resource types. GenericScimResource

Some grey areas:

  • You already have a set of bean classes that model your domain objects. In this case, you may or may not be able to retrofit your existing classes to derive from BaseScimResource. You may at least be able to implement the ScimResource interface.
  • You have what ought to be a suitable set of model classes, but the actual resources returned by your SCIM service provider differ in some way. For example, you might be working with a service provider that ostensibly supports the RFC 7643 "urn:ietf:params:scim:schemas:core:2.0:User" schema but which adds non-standard attributes to the core schema. Unfortunately, the UserResource model class will not work in this case. One option would be to create a new class that extends UserResource and adds the non-standard attributes as fields. Another option would be to go ahead and use GenericScimResource.