From 65f47d9c24da91fdeac1eb6f012f44cc056ac4e4 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Thu, 25 Aug 2016 15:15:46 +0000
Subject: [PATCH] OPENDJ-3160 Support DN templates in reference property base DNs

---
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceImpl.java |   68 +++++++++++++++++++++------------
 1 files changed, 43 insertions(+), 25 deletions(-)

diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceImpl.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceImpl.java
index 5e6f81c..7cec13f 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceImpl.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceImpl.java
@@ -34,6 +34,8 @@
 import static org.forgerock.opendj.ldap.SearchScope.SINGLE_LEVEL;
 import static org.forgerock.opendj.ldap.requests.Requests.*;
 import static org.forgerock.opendj.rest2ldap.ReadOnUpdatePolicy.CONTROLS;
+import static org.forgerock.opendj.rest2ldap.RoutingContext.newCollectionRoutingContext;
+import static org.forgerock.opendj.rest2ldap.RoutingContext.newRoutingContext;
 import static org.forgerock.opendj.rest2ldap.Utils.connectionFrom;
 import static org.forgerock.opendj.rest2ldap.Utils.newBadRequestException;
 import static org.forgerock.opendj.rest2ldap.Utils.newNotSupportedException;
@@ -260,10 +262,15 @@
             return e.asPromise();
         }
 
+        // Temporary routing context which will be used for encoding LDAP attributes. Note that the DN represents the
+        // DN of the collection, not the resource being created. The DN of the resource can only be determined once
+        // the LDAP attributes have been encoded.
+        final RoutingContext parentDnAndType = newCollectionRoutingContext(context, baseDn, subType);
+
         // Now build the LDAP representation and add it.
         final Connection connection = connectionFrom(context);
         return subType.getPropertyMapper()
-                      .create(context, subType, ROOT, request.getContent())
+                      .create(parentDnAndType, subType, ROOT, request.getContent())
                       .thenAsync(new AsyncFunction<List<Attribute>, ResourceResponse, ResourceException>() {
                           @Override
                           public Promise<ResourceResponse, ResourceException> apply(final List<Attribute> attributes) {
@@ -284,9 +291,12 @@
                                           getLdapAttributesForKnownType(request.getFields(), subType);
                                   addRequest.addControl(PostReadRequestControl.newControl(false, ldapAttributes));
                               }
+                              // Use a routing context which refers to the created entry when computing the response.
+                              final RoutingContext dnAndType =
+                                      newRoutingContext(context, addRequest.getName(), subType);
                               return connection.addAsync(addRequest)
                                                .thenCatchAsync(lazilyAddGlueEntry(connection, addRequest))
-                                               .thenAsync(encodeUpdateResourceResponse(context, subType),
+                                               .thenAsync(encodeUpdateResourceResponse(dnAndType, subType),
                                                           adaptLdapException(ResourceResponse.class));
                           }
                       });
@@ -346,7 +356,7 @@
                         return connection.applyChangeAsync(deleteRequest)
                                          .thenCatchAsync(deleteSubtreeWithoutUsingSubtreeDeleteControl(connection,
                                                                                                        deleteRequest))
-                                         .thenAsync(encodeUpdateResourceResponse(context, dnAndType.getType()),
+                                         .thenAsync(encodeUpdateResourceResponse(dnAndType, dnAndType.getType()),
                                                     adaptLdapException(ResourceResponse.class));
                     }
                 });
@@ -433,7 +443,7 @@
                         final Resource subType = dnAndType.getType();
                         final PropertyMapper propertyMapper = subType.getPropertyMapper();
                         for (final PatchOperation operation : request.getPatchOperations()) {
-                            promises.add(propertyMapper.patch(context, subType, ROOT, operation));
+                            promises.add(propertyMapper.patch(dnAndType, subType, ROOT, operation));
                         }
                         return when(promises);
                     }
@@ -457,7 +467,7 @@
                         if (modifyRequest.getModifications().isEmpty()) {
                             // This patch is a no-op so just read the entry and check its version.
                             return connection.readEntryAsync(dnAndType.getDn(), attributes)
-                                             .thenAsync(encodeEmptyPatchResourceResponse(context, subType, request),
+                                             .thenAsync(encodeEmptyPatchResourceResponse(dnAndType, subType, request),
                                                         adaptLdapException(ResourceResponse.class));
                         } else {
                             // Add controls and perform the modify request.
@@ -469,7 +479,7 @@
                             }
                             addAssertionControl(modifyRequest, request.getRevision());
                             return connection.applyChangeAsync(modifyRequest)
-                                             .thenAsync(encodeUpdateResourceResponse(context, subType),
+                                             .thenAsync(encodeUpdateResourceResponse(dnAndType, subType),
                                                         adaptLdapException(ResourceResponse.class));
                         }
                     }
@@ -503,6 +513,9 @@
         if (queryFilter == null) {
             return new BadRequestException(ERR_QUERY_BY_ID_OR_EXPRESSION_NOT_SUPPORTED.get().toString()).asPromise();
         }
+
+        // Temporary routing context which will be used for encoding the LDAP filter.
+        final RoutingContext parentDnAndType = newCollectionRoutingContext(context, baseDn, resource);
         final PropertyMapper propertyMapper = resource.getPropertyMapper();
         final QueryFilterVisitor<Promise<Filter, ResourceException>, Void, JsonPointer> visitor =
                 new QueryFilterVisitor<Promise<Filter, ResourceException>, Void, JsonPointer>() {
@@ -549,14 +562,14 @@
                     public Promise<Filter, ResourceException> visitContainsFilter(
                             final Void unused, final JsonPointer field, final Object valueAssertion) {
                         return propertyMapper.getLdapFilter(
-                                context, resource, ROOT, field, CONTAINS, null, valueAssertion);
+                                parentDnAndType, resource, ROOT, field, CONTAINS, null, valueAssertion);
                     }
 
                     @Override
                     public Promise<Filter, ResourceException> visitEqualsFilter(
                             final Void unused, final JsonPointer field, final Object valueAssertion) {
                         return propertyMapper.getLdapFilter(
-                                context, resource, ROOT, field, EQUAL_TO, null, valueAssertion);
+                                parentDnAndType, resource, ROOT, field, EQUAL_TO, null, valueAssertion);
                     }
 
                     @Override
@@ -565,35 +578,35 @@
                                                                                        final String operator,
                                                                                        final Object valueAssertion) {
                         return propertyMapper.getLdapFilter(
-                                context, resource, ROOT, field, EXTENDED, operator, valueAssertion);
+                                parentDnAndType, resource, ROOT, field, EXTENDED, operator, valueAssertion);
                     }
 
                     @Override
                     public Promise<Filter, ResourceException> visitGreaterThanFilter(
                             final Void unused, final JsonPointer field, final Object valueAssertion) {
                         return propertyMapper.getLdapFilter(
-                                context, resource, ROOT, field, GREATER_THAN, null, valueAssertion);
+                                parentDnAndType, resource, ROOT, field, GREATER_THAN, null, valueAssertion);
                     }
 
                     @Override
                     public Promise<Filter, ResourceException> visitGreaterThanOrEqualToFilter(
                             final Void unused, final JsonPointer field, final Object valueAssertion) {
                         return propertyMapper.getLdapFilter(
-                                context, resource, ROOT, field, GREATER_THAN_OR_EQUAL_TO, null, valueAssertion);
+                                parentDnAndType, resource, ROOT, field, GREATER_THAN_OR_EQUAL_TO, null, valueAssertion);
                     }
 
                     @Override
                     public Promise<Filter, ResourceException> visitLessThanFilter(
                             final Void unused, final JsonPointer field, final Object valueAssertion) {
                         return propertyMapper.getLdapFilter(
-                                context, resource, ROOT, field, LESS_THAN, null, valueAssertion);
+                                parentDnAndType, resource, ROOT, field, LESS_THAN, null, valueAssertion);
                     }
 
                     @Override
                     public Promise<Filter, ResourceException> visitLessThanOrEqualToFilter(
                             final Void unused, final JsonPointer field, final Object valueAssertion) {
                         return propertyMapper.getLdapFilter(
-                                context, resource, ROOT, field, LESS_THAN_OR_EQUAL_TO, null, valueAssertion);
+                                parentDnAndType, resource, ROOT, field, LESS_THAN_OR_EQUAL_TO, null, valueAssertion);
                     }
 
                     @Override
@@ -649,14 +662,15 @@
                     @Override
                     public Promise<Filter, ResourceException> visitPresentFilter(
                             final Void unused, final JsonPointer field) {
-                        return propertyMapper.getLdapFilter(context, resource, ROOT, field, PRESENT, null, null);
+                        return propertyMapper.getLdapFilter(
+                                parentDnAndType, resource, ROOT, field, PRESENT, null, null);
                     }
 
                     @Override
                     public Promise<Filter, ResourceException> visitStartsWithFilter(
                             final Void unused, final JsonPointer field, final Object valueAssertion) {
                         return propertyMapper.getLdapFilter(
-                                context, resource, ROOT, field, STARTS_WITH, null, valueAssertion);
+                                parentDnAndType, resource, ROOT, field, STARTS_WITH, null, valueAssertion);
                     }
                 };
         // Note that the returned LDAP filter may be null if it could not be mapped by any property mappers.
@@ -741,8 +755,9 @@
                         final String id = namingStrategy.decodeResourceId(entry);
                         final String revision = getRevisionFromEntry(entry);
                         final Resource subType = resource.resolveSubTypeFromObjectClasses(entry);
+                        final RoutingContext dnAndType = newRoutingContext(context, entry.getName(), subType);
                         final PropertyMapper propertyMapper = subType.getPropertyMapper();
-                        propertyMapper.read(context, subType, ROOT, entry)
+                        propertyMapper.read(dnAndType, subType, ROOT, entry)
                                       .thenOnResult(new ResultHandler<JsonValue>() {
                                           @Override
                                           public void handleResult(final JsonValue result) {
@@ -845,7 +860,8 @@
                              @Override
                              public Promise<ResourceResponse, ResourceException> apply(SearchResultEntry entry) {
                                  final Resource subType = resource.resolveSubTypeFromObjectClasses(entry);
-                                 return encodeResourceResponse(context, subType, entry);
+                                 final RoutingContext dnAndType = newRoutingContext(context, entry.getName(), subType);
+                                 return encodeResourceResponse(dnAndType, subType, entry);
                              }
                          });
     }
@@ -854,7 +870,7 @@
             final Context context, final String resourceId, final UpdateRequest request) {
         final Connection connection = connectionFrom(context);
         final AtomicReference<Entry> entryHolder = new AtomicReference<>();
-        final AtomicReference<Resource> subTypeHolder = new AtomicReference<>();
+        final AtomicReference<RoutingContext> dnAndTypeHolder = new AtomicReference<>();
         return connection
                 .searchSingleEntryAsync(searchRequestForUnknownType(resourceId, Collections.<JsonPointer>emptyList()))
                 .thenCatchAsync(adaptLdapException(SearchResultEntry.class))
@@ -869,18 +885,20 @@
 
                         // Determine the type of resource and set of changes that need to be performed.
                         final Resource subType = resource.resolveSubTypeFromObjectClasses(entry);
-                        subTypeHolder.set(subType);
+                        final RoutingContext dnAndType = newRoutingContext(context, entry.getName(), subType);
+                        dnAndTypeHolder.set(dnAndType);
                         final PropertyMapper propertyMapper = subType.getPropertyMapper();
-                        return propertyMapper.update(context, subType , ROOT, entry, request.getContent());
+                        return propertyMapper.update(dnAndType, subType , ROOT, entry, request.getContent());
                     }
                 }).thenAsync(new AsyncFunction<List<Modification>, ResourceResponse, ResourceException>() {
                     @Override
                     public Promise<ResourceResponse, ResourceException> apply(List<Modification> modifications)
                             throws ResourceException {
-                        final Resource subType = subTypeHolder.get();
+                        final RoutingContext dnAndType = dnAndTypeHolder.get();
+                        final Resource subType = dnAndType.getType();
                         if (modifications.isEmpty()) {
                             // No changes to be performed so just return the entry that we read.
-                            return encodeResourceResponse(context, subType, entryHolder.get());
+                            return encodeResourceResponse(dnAndType, subType, entryHolder.get());
                         }
                         // Perform the modify operation.
                         final ModifyRequest modifyRequest = newModifyRequest(entryHolder.get().getName());
@@ -894,7 +912,7 @@
                         addAssertionControl(modifyRequest, request.getRevision());
                         modifyRequest.getModifications().addAll(modifications);
                         return connection.applyChangeAsync(modifyRequest)
-                                         .thenAsync(encodeUpdateResourceResponse(context, subType),
+                                         .thenAsync(encodeUpdateResourceResponse(dnAndType, subType),
                                                     adaptLdapException(ResourceResponse.class));
                     }
                 });
@@ -928,7 +946,7 @@
         final SearchRequest searchRequest = namingStrategy.createSearchRequest(baseDn, resourceId);
         if (searchRequest.getScope().equals(BASE_OBJECT) && !resource.hasSubTypes()) {
             // There's no point in doing a search because we already know the DN and sub-resources.
-            return newResultPromise(new RoutingContext(context, searchRequest.getName(), resource));
+            return newResultPromise(newRoutingContext(context, searchRequest.getName(), resource));
         }
         if (etagAttribute != null && revision != null) {
             searchRequest.addAttribute(etagAttribute.toString());
@@ -943,7 +961,7 @@
                                  // Fail-fast if there is a version mismatch.
                                  ensureMvccVersionMatches(entry, revision);
                                  final Resource subType = resource.resolveSubTypeFromObjectClasses(entry);
-                                 return newResultPromise(new RoutingContext(context, entry.getName(), subType));
+                                 return newResultPromise(newRoutingContext(context, entry.getName(), subType));
                              }
                          }, adaptLdapException(RoutingContext.class));
     }

--
Gitblit v1.10.0