You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by bd...@apache.org on 2022/08/26 19:01:17 UTC

[directory-scimple] 01/01: Add tests for BulkResourceImpl

This is an automated email from the ASF dual-hosted git repository.

bdemers pushed a commit to branch bulk-resource-test
in repository https://gitbox.apache.org/repos/asf/directory-scimple.git

commit 5ba6f401915b0123acd20f9ca50b5edc585243fd
Author: Brian Demers <bd...@apache.org>
AuthorDate: Fri Aug 26 15:01:10 2022 -0400

    Add tests for BulkResourceImpl
    
    Few minor fixes found along the way:
    * null checks
    * Object mapper should be transient
    * Missing ReferenceTypes (shown in the Protocol Spec, in the bulk resource section)
---
 .../scim/server/rest/BulkResourceImpl.java         |  72 ++++---
 .../scim/server/rest/BulkResourceImplTest.java     | 230 +++++++++++++++++++++
 .../scim/spec/json/ObjectMapperFactory.java        |   6 +
 .../scim/spec/resources/ScimResource.java          |   2 +-
 .../scim/spec/schema/ResourceReference.java        |   4 +-
 5 files changed, 281 insertions(+), 33 deletions(-)

diff --git a/scim-server/src/main/java/org/apache/directory/scim/server/rest/BulkResourceImpl.java b/scim-server/src/main/java/org/apache/directory/scim/server/rest/BulkResourceImpl.java
index 85a0632..00f2a78 100644
--- a/scim-server/src/main/java/org/apache/directory/scim/server/rest/BulkResourceImpl.java
+++ b/scim-server/src/main/java/org/apache/directory/scim/server/rest/BulkResourceImpl.java
@@ -19,14 +19,7 @@
 
 package org.apache.directory.scim.server.rest;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 import java.util.regex.Pattern;
 
 import jakarta.enterprise.context.ApplicationScoped;
@@ -35,6 +28,7 @@ import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.core.Response.Status;
 import jakarta.ws.rs.core.UriInfo;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.directory.scim.server.exception.UnableToCreateResourceException;
 import org.apache.directory.scim.server.exception.UnableToDeleteResourceException;
 import org.apache.directory.scim.server.exception.UnableToRetrieveResourceException;
@@ -112,9 +106,9 @@ public class BulkResourceImpl implements BulkResource {
   public Response doBulk(BulkRequest request, UriInfo uriInfo) {
     BulkResponse response;
     int errorCount = 0;
-    int requestFailOnErrors = request.getFailOnErrors();
-    int maxErrorCount = requestFailOnErrors > 0 ? requestFailOnErrors : Integer.MAX_VALUE;
-    int errorCountIncrement = requestFailOnErrors > 0 ? 1 : 0;
+    Integer requestFailOnErrors = request.getFailOnErrors();
+    int maxErrorCount = requestFailOnErrors != null && requestFailOnErrors > 0 ? requestFailOnErrors : Integer.MAX_VALUE;
+    int errorCountIncrement = requestFailOnErrors == null || requestFailOnErrors > 0 ? 1 : 0;
     List<BulkOperation> bulkOperations = request.getOperations();
     Map<String, BulkOperation> bulkIdKeyToOperationResult = new HashMap<>();
     List<IWishJavaHadTuples> allUnresolveds = new ArrayList<>();
@@ -211,7 +205,7 @@ public class BulkResourceImpl implements BulkResource {
         operationRequest.setData(null);
 
         if (bulkIdKey != null) {
-          Set<String> reverseDependencies = transitiveReverseDependencies.get(bulkIdKey);
+          Set<String> reverseDependencies = transitiveReverseDependencies.getOrDefault(bulkIdKey, Collections.emptySet());
           String detail = String.format(OPERATION_DEPENDS_ON_FAILED_OPERATION, bulkIdKey);
 
           for (String dependentBulkIdKey : reverseDependencies) {
@@ -225,9 +219,11 @@ public class BulkResourceImpl implements BulkResource {
         }
       }
     }
+
+    boolean errorCountExceeded = false;
+
     // do the operations
     for (BulkOperation operationResult : bulkOperations) {
-      boolean errorCountExceeded = errorCount >= maxErrorCount;
 
       if (!errorCountExceeded && !(operationResult.getResponse() instanceof ErrorResponse)) {
         try {
@@ -236,6 +232,7 @@ public class BulkResourceImpl implements BulkResource {
           log.error("Failed to do bulk operation", resourceException);
 
           errorCount += errorCountIncrement;
+          errorCountExceeded = errorCount >= maxErrorCount;
 
           String detail = resourceException.getLocalizedMessage();
           createAndSetErrorResponse(operationResult, resourceException.getStatus(), detail);
@@ -244,6 +241,7 @@ public class BulkResourceImpl implements BulkResource {
             String bulkIdKey = "bulkId:" + operationResult.getBulkId();
 
             this.cleanup(bulkIdKey, transitiveReverseDependencies, bulkIdKeyToOperationResult);
+            operationResult.setData(null);
           }
         } catch (UnresolvableOperationException unresolvableOperationException) {
           log.error("Could not resolve bulkId during Bulk Operation method handling", unresolvableOperationException);
@@ -257,11 +255,14 @@ public class BulkResourceImpl implements BulkResource {
             String bulkIdKey = "bulkId:" + operationResult.getBulkId();
 
             this.cleanup(bulkIdKey, transitiveReverseDependencies, bulkIdKeyToOperationResult);
+            operationResult.setData(null);
           }
         }
       } else if (errorCountExceeded) {
-        createAndSetErrorResponse(operationResult, Status.CONFLICT, "failOnErrors count reached");
 
+        // continue processing bulk operations to cleanup any dependencies
+
+        createAndSetErrorResponse(operationResult, Status.CONFLICT, "failOnErrors count reached");
         if (operationResult.getBulkId() != null) {
           String bulkIdKey = "bulkId:" + operationResult.getBulkId();
 
@@ -318,12 +319,16 @@ public class BulkResourceImpl implements BulkResource {
         this.cleanup(bulkIdKey, transitiveReverseDependencies, bulkIdKeyToOperationResult);
       }
     }
-    response = new BulkResponse();
-    response.setOperations(bulkOperations);
-    response.setStatus(Status.OK);
 
-    return Response.ok(response)
-                   .build();
+    Status status = errorCountExceeded ? Status.BAD_REQUEST : Status.OK;
+
+    response = new BulkResponse()
+      .setOperations(bulkOperations)
+      .setStatus(status);
+
+    return Response.status(status)
+      .entity(response)
+      .build();
   }
 
   /**
@@ -335,7 +340,7 @@ public class BulkResourceImpl implements BulkResource {
    * @param bulkIdKeyToOperationResult
    */
   private void cleanup(String bulkIdKeyToCleanup, Map<String, Set<String>> transitiveReverseDependencies, Map<String, BulkOperation> bulkIdKeyToOperationResult) {
-    Set<String> reverseDependencies = transitiveReverseDependencies.get(bulkIdKeyToCleanup);
+    Set<String> reverseDependencies = transitiveReverseDependencies.getOrDefault(bulkIdKeyToCleanup, Collections.emptySet());
     BulkOperation operationResult = bulkIdKeyToOperationResult.get(bulkIdKeyToCleanup);
     String bulkId = operationResult.getBulkId();
     ScimResource scimResource = operationResult.getData();
@@ -344,7 +349,9 @@ public class BulkResourceImpl implements BulkResource {
     Repository<ScimResource> repository = this.repositoryRegistry.getRepository(scimResourceClass);
 
     try {
-      repository.delete(scimResource.getId());
+      if (StringUtils.isNotBlank(scimResource.getId())) {
+        repository.delete(scimResource.getId());
+      }
     } catch (UnableToDeleteResourceException unableToDeleteResourceException) {
       log.error("Could not delete ScimResource after failure: {}", scimResource);
     }
@@ -426,7 +433,8 @@ public class BulkResourceImpl implements BulkResource {
                                   .setData(newScimResource);
       }
       operationResult.setData(newScimResource);
-      operationResult.setLocation(newResourceUri.toString());
+      operationResult.setLocation(newResourceUri);
+      operationResult.setPath(null);
       operationResult.setStatus(StatusWrapper.wrap(Status.CREATED));
     }
       break;
@@ -481,6 +489,7 @@ public class BulkResourceImpl implements BulkResource {
     ErrorResponse error = new ErrorResponse(status, detail);
     operationResult.setResponse(error);
     operationResult.setStatus(new StatusWrapper(status));
+    operationResult.setPath(null);
   }
 
   @AllArgsConstructor
@@ -726,8 +735,7 @@ public class BulkResourceImpl implements BulkResource {
     if (!root.equals(current) && !visited.contains(current)) {
       visited.add(current);
 
-      Set<String> dependencies = dependencyGraph.get(current);
-
+      Set<String> dependencies = dependencyGraph.getOrDefault(current, Collections.emptySet());
       for (String dependency : dependencies) {
         generateVisited(visited, dependencyGraph, root, dependency);
       }
@@ -772,13 +780,15 @@ public class BulkResourceImpl implements BulkResource {
                                                         // all COMPLEXES are
                                                         // multiValued
         Object attributeObject = scimObjectAttribute.getAccessor().get(scimObject);
-        Class<?> attributeObjectClass = attributeObject.getClass();
-        boolean isCollection = Collection.class.isAssignableFrom(attributeObjectClass);
-        Collection<?> attributeValues = isCollection ? (Collection<?>) attributeObject : Arrays.asList(attributeObject);
-        List<Schema.Attribute> subAttributes = scimObjectAttribute.getAttributes();
-
-        for (Object attributeValue : attributeValues) {
-          generateReverseDependenciesGraph(reverseDependenciesGraph, dependentBulkId, attributeValue, subAttributes);
+        if (attributeObject != null) {
+          Class<?> attributeObjectClass = attributeObject.getClass();
+          boolean isCollection = Collection.class.isAssignableFrom(attributeObjectClass);
+          Collection<?> attributeValues = isCollection ? (Collection<?>) attributeObject : List.of(attributeObject);
+          List<Schema.Attribute> subAttributes = scimObjectAttribute.getAttributes();
+
+          for (Object attributeValue : attributeValues) {
+            generateReverseDependenciesGraph(reverseDependenciesGraph, dependentBulkId, attributeValue, subAttributes);
+          }
         }
       } else if (scimObjectAttribute.getType() == Schema.Attribute.Type.COMPLEX) {
         Object attributeValue = scimObjectAttribute.getAccessor().get(scimObject);
diff --git a/scim-server/src/test/java/org/apache/directory/scim/server/rest/BulkResourceImplTest.java b/scim-server/src/test/java/org/apache/directory/scim/server/rest/BulkResourceImplTest.java
new file mode 100644
index 0000000..514773d
--- /dev/null
+++ b/scim-server/src/test/java/org/apache/directory/scim/server/rest/BulkResourceImplTest.java
@@ -0,0 +1,230 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.directory.scim.server.rest;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import jakarta.enterprise.inject.Instance;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.UriBuilder;
+import jakarta.ws.rs.core.UriInfo;
+import org.apache.directory.scim.server.exception.UnableToCreateResourceException;
+import org.apache.directory.scim.server.repository.Repository;
+import org.apache.directory.scim.server.repository.RepositoryRegistry;
+import org.apache.directory.scim.server.schema.SchemaRegistry;
+import org.apache.directory.scim.spec.extension.ScimExtensionRegistry;
+import org.apache.directory.scim.spec.json.ObjectMapperFactory;
+import org.apache.directory.scim.spec.protocol.data.BulkOperation;
+import org.apache.directory.scim.spec.protocol.data.BulkRequest;
+import org.apache.directory.scim.spec.protocol.data.BulkResponse;
+import org.apache.directory.scim.spec.protocol.data.ErrorResponse;
+import org.apache.directory.scim.spec.resources.ScimGroup;
+import org.apache.directory.scim.spec.resources.ScimResource;
+import org.apache.directory.scim.spec.resources.ScimUser;
+import org.apache.directory.scim.spec.schema.ResourceReference;
+import org.junit.jupiter.api.Test;
+import org.mockito.InOrder;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Stream;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class BulkResourceImplTest {
+
+  @Test
+  public void bulkIdTemporaryIdentifiersTest() throws Exception {
+    // Request copied from RFC 7644 section 3.7.2
+    ScimUser alice = new ScimUser()
+      .setUserName("Alice");
+
+    ScimGroup tourGuides = new ScimGroup()
+      .setDisplayName("Tour Guides")
+      .setMembers(List.of(new ResourceReference()
+        .setType(ResourceReference.ReferenceType.USER)
+        .setValue("bulkId:qwerty")));
+
+    BulkRequest bulkRequest = new BulkRequest()
+      .setOperations(List.of(
+        new BulkOperation()
+          .setMethod(BulkOperation.Method.POST)
+          .setPath("/Users")
+          .setBulkId("qwerty")
+          .setData(alice),
+        new BulkOperation()
+          .setMethod(BulkOperation.Method.POST)
+          .setPath("/Groups")
+          .setBulkId("ytrewq")
+          .setData(tourGuides)));
+
+    Instance<Repository<? extends ScimResource>> emptyInstance = mock(Instance.class);
+    when(emptyInstance.stream()).thenReturn(Stream.empty());
+
+    SchemaRegistry schemaRegistry = new SchemaRegistry();
+    RepositoryRegistry repositoryRegistry = new RepositoryRegistry(schemaRegistry, ScimExtensionRegistry.getInstance(), emptyInstance);
+
+    Instance<Repository<ScimUser>> userRepositoryInstance = mock(Instance.class);
+    Repository<ScimUser> userRepository = mock(Repository.class);
+    ScimUser user = new ScimUser();
+    user.setId("alice-id");
+    when(userRepositoryInstance.get()).thenReturn(userRepository);
+    repositoryRegistry.registerRepository(ScimUser.class, userRepository);
+    when(userRepository.create(any())).thenReturn(user);
+
+    Instance<Repository<ScimGroup>> groupProviderInstance = mock(Instance.class);
+    Repository<ScimGroup> groupRepository = mock(Repository.class);
+    ScimGroup group = new ScimGroup();
+    group.setId("tour-guides");
+    when(groupProviderInstance.get()).thenReturn(groupRepository);
+    repositoryRegistry.registerRepository(ScimGroup.class, groupRepository);
+    when(groupRepository.getExtensionList()).thenReturn(Collections.emptyList());
+    when(groupRepository.create(any())).thenReturn(group);
+
+    BulkResourceImpl impl = new BulkResourceImpl(schemaRegistry, repositoryRegistry);
+    UriInfo uriInfo = mock(UriInfo.class);
+    UriBuilder uriBuilder = mock(UriBuilder.class);
+    when(uriInfo.getBaseUriBuilder()).thenReturn(uriBuilder);
+    when(uriBuilder.path("/Users")).thenReturn(uriBuilder);
+    when(uriBuilder.path("alice-id")).thenReturn(uriBuilder);
+    when(uriBuilder.build())
+      .thenReturn(URI.create("https://scim.example.com/Users/alice-id"))
+      .thenReturn(URI.create("https://scim.example.com/Groups/tour-guides"));
+    when(uriBuilder.path("/Groups")).thenReturn(uriBuilder);
+    when(uriBuilder.path("tour-guides")).thenReturn(uriBuilder);
+
+    Response response = impl.doBulk(bulkRequest, uriInfo);
+    BulkResponse bulkResponse = (BulkResponse) response.getEntity();
+
+    assertThat(bulkResponse.getErrorResponse()).isNull();
+    assertThat(bulkResponse.getStatus()).isEqualTo(Response.Status.OK);
+    assertThat(bulkResponse.getSchemas()).containsOnly(BulkResponse.SCHEMA_URI);
+    assertThat(bulkResponse.getOperations())
+      .hasSize(2)
+      .contains(new BulkOperation()
+        .setMethod(BulkOperation.Method.POST)
+        .setBulkId("qwerty")
+        .setData(new ScimUser()
+          .setId("alice-id"))
+        .setLocation("https://scim.example.com/Users/alice-id")
+        .setStatus(new BulkOperation.StatusWrapper(Response.Status.CREATED)))
+      .contains(new BulkOperation()
+        .setMethod(BulkOperation.Method.POST)
+        .setBulkId("ytrewq")
+        .setData(new ScimGroup()
+          .setId("tour-guides"))
+        .setLocation("https://scim.example.com/Groups/tour-guides")
+        .setStatus(new BulkOperation.StatusWrapper(Response.Status.CREATED)));
+
+    // Verify behavior
+    InOrder inOrder = inOrder(userRepository, groupRepository);
+    inOrder.verify(userRepository, atLeast(1)).getExtensionList();
+    inOrder.verify(groupRepository, atLeast(1)).getExtensionList();
+
+    // User was created before group due to calculated dependency
+    inOrder.verify(userRepository).create(alice);
+    inOrder.verify(groupRepository).create(tourGuides);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void bulkFailedTest() throws Exception {
+    ScimUser alice = new ScimUser()
+      .setUserName("Alice");
+    ScimUser bob = new ScimUser()
+      .setUserName("Bob");
+
+    BulkRequest bulkRequest = new BulkRequest()
+      .setFailOnErrors(1)
+      .setOperations(List.of(
+        new BulkOperation()
+          .setMethod(BulkOperation.Method.POST)
+          .setPath("/Users")
+          .setBulkId("bulk-id-alice")
+          .setData(alice),
+        new BulkOperation()
+          .setMethod(BulkOperation.Method.POST)
+          .setPath("/Users")
+          .setBulkId("bulk-id-bob")
+          .setData(bob)
+      ));
+    Instance<Repository<? extends ScimResource>> emptyInstance = mock(Instance.class);
+    when(emptyInstance.stream()).thenReturn(Stream.empty());
+
+    SchemaRegistry schemaRegistry = new SchemaRegistry();
+    RepositoryRegistry repositoryRegistry = new RepositoryRegistry(schemaRegistry, ScimExtensionRegistry.getInstance(), emptyInstance );
+
+    Instance<Repository<ScimUser>> userProviderInstance = mock(Instance.class);
+    Repository<ScimUser> userRepository = mock(Repository.class);
+
+    when(userProviderInstance.get()).thenReturn(userRepository);
+    repositoryRegistry.registerRepository(ScimUser.class, userRepository);
+    when(userProviderInstance.get()).thenReturn(userRepository);
+
+    ScimUser userAlice = new ScimUser();
+    userAlice.setId("alice-id");
+
+    ScimUser userBob = new ScimUser();
+    userBob.setId("bob-id");
+
+    when(userRepository.create(any()))
+      .thenReturn(userAlice)
+      .thenThrow(new UnableToCreateResourceException(Response.Status.BAD_REQUEST, "Expected Test Exception when bob is created"));
+
+
+    BulkResourceImpl impl = new BulkResourceImpl(schemaRegistry, repositoryRegistry);
+    UriInfo uriInfo = mock(UriInfo.class);
+    UriBuilder uriBuilder = mock(UriBuilder.class);
+    when(uriInfo.getBaseUriBuilder()).thenReturn(uriBuilder);
+    when(uriBuilder.path("/Users")).thenReturn(uriBuilder);
+    when(uriBuilder.path("alice-id")).thenReturn(uriBuilder);
+    when(uriBuilder.path("bob-id")).thenReturn(uriBuilder);
+    when(uriBuilder.build())
+      .thenReturn(URI.create("https://scim.example.com/Users/alice-id"))
+      .thenReturn(URI.create("https://scim.example.com/Users/bob-id"));
+
+    Response response = impl.doBulk(bulkRequest, uriInfo);
+    BulkResponse bulkResponse = (BulkResponse) response.getEntity();
+
+    assertThat(bulkResponse.getErrorResponse()).isNull();
+    assertThat(bulkResponse.getStatus()).isEqualTo(Response.Status.BAD_REQUEST);
+    assertThat(bulkResponse.getSchemas()).containsOnly(BulkResponse.SCHEMA_URI);
+    assertThat(bulkResponse.getOperations())
+      .hasSize(2)
+      .contains(new BulkOperation()
+        .setMethod(BulkOperation.Method.POST)
+        .setBulkId("bulk-id-alice")
+        .setData(new ScimUser()
+          .setId("alice-id"))
+        .setLocation("https://scim.example.com/Users/alice-id")
+        .setStatus(new BulkOperation.StatusWrapper(Response.Status.CREATED)))
+      .contains(new BulkOperation()
+        .setMethod(BulkOperation.Method.POST)
+        .setBulkId("bulk-id-bob")
+        .setResponse(new ErrorResponse(Response.Status.BAD_REQUEST, "Expected Test Exception when bob is created"))
+        .setStatus(new BulkOperation.StatusWrapper(Response.Status.BAD_REQUEST)));
+  }
+}
diff --git a/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/json/ObjectMapperFactory.java b/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/json/ObjectMapperFactory.java
index 6421434..747006c 100644
--- a/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/json/ObjectMapperFactory.java
+++ b/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/json/ObjectMapperFactory.java
@@ -29,7 +29,13 @@ import com.fasterxml.jackson.module.jakarta.xmlbind.JakartaXmlBindAnnotationModu
 
 public class ObjectMapperFactory {
 
+  private final static ObjectMapper objectMapper = createObjectMapper();
+
   public static ObjectMapper getObjectMapper() {
+    return objectMapper;
+  }
+
+  private static ObjectMapper createObjectMapper() {
     ObjectMapper objectMapper = new ObjectMapper();
 
     objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
diff --git a/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/resources/ScimResource.java b/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/resources/ScimResource.java
index 596a244..c84d9ca 100644
--- a/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/resources/ScimResource.java
+++ b/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/resources/ScimResource.java
@@ -59,7 +59,7 @@ public abstract class ScimResource extends BaseResource implements Serializable
 
   private static final Logger LOG = LoggerFactory.getLogger(ScimResource.class);
 
-  private final ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();
+  private transient final ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();
 
   @XmlElement
   @NotNull
diff --git a/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/schema/ResourceReference.java b/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/schema/ResourceReference.java
index 55af15f..d142c75 100644
--- a/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/schema/ResourceReference.java
+++ b/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/schema/ResourceReference.java
@@ -42,7 +42,9 @@ public class ResourceReference implements Serializable {
   @XmlEnum
   public enum ReferenceType {
     @XmlEnumValue("direct") DIRECT,
-    @XmlEnumValue("indirect") INDIRECT
+    @XmlEnumValue("indirect") INDIRECT,
+    @XmlEnumValue("User") USER,
+    @XmlEnumValue("Group") GROUP;
   }
   
   @ScimAttribute(description="Reference Element Identifier")