From a08c81f677247ec9eb7721a86250c663065e9930 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Wed, 22 Jun 2016 22:12:03 +0000
Subject: [PATCH] OPENDJ-2871 Add support for sub-resources and inheritance

---
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferencePropertyMapper.java |  186 ++++++++++++++++++++++++----------------------
 1 files changed, 97 insertions(+), 89 deletions(-)

diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferencePropertyMapper.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferencePropertyMapper.java
index 52291a1..e01c530 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferencePropertyMapper.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferencePropertyMapper.java
@@ -15,12 +15,14 @@
  */
 package org.forgerock.opendj.rest2ldap;
 
+import static org.forgerock.opendj.ldap.ResultCode.ADMIN_LIMIT_EXCEEDED;
 import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*;
 import static org.forgerock.opendj.ldap.LdapException.newLdapException;
 import static org.forgerock.opendj.ldap.requests.Requests.newSearchRequest;
 import static org.forgerock.opendj.rest2ldap.Rest2Ldap.asResourceException;
-import static org.forgerock.opendj.rest2ldap.Utils.newBadRequestException;
 import static org.forgerock.util.Reject.checkNotNull;
+import static org.forgerock.opendj.rest2ldap.Utils.newBadRequestException;
+import static org.forgerock.util.promise.Promises.newResultPromise;
 
 import java.util.ArrayList;
 import java.util.LinkedHashSet;
@@ -44,7 +46,6 @@
 import org.forgerock.opendj.ldap.LdapException;
 import org.forgerock.opendj.ldap.LinkedAttribute;
 import org.forgerock.opendj.ldap.MultipleEntriesFoundException;
-import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.SearchResultHandler;
 import org.forgerock.opendj.ldap.SearchScope;
 import org.forgerock.opendj.ldap.requests.SearchRequest;
@@ -133,10 +134,11 @@
     }
 
     @Override
-    Promise<Filter, ResourceException> getLdapFilter(final Connection connection, final JsonPointer path,
-                                                     final JsonPointer subPath, final FilterType type,
-                                                     final String operator, final Object valueAssertion) {
-        return mapper.getLdapFilter(connection, path, subPath, type, operator, valueAssertion)
+    Promise<Filter, ResourceException> getLdapFilter(final Connection connection, final Resource resource,
+                                                     final JsonPointer path, final JsonPointer subPath,
+                                                     final FilterType type, final String operator,
+                                                     final Object valueAssertion) {
+        return mapper.getLdapFilter(connection, resource, path, subPath, type, operator, valueAssertion)
                 .thenAsync(new AsyncFunction<Filter, Filter, ResourceException>() {
                     @Override
                     public Promise<Filter, ResourceException> apply(final Filter result) {
@@ -164,8 +166,7 @@
                             @Override
                             public Filter apply(Result result) throws ResourceException {
                                 if (subFilters.size() >= SEARCH_MAX_CANDIDATES) {
-                                    throw asResourceException(
-                                            newLdapException(ResultCode.ADMIN_LIMIT_EXCEEDED));
+                                    throw asResourceException(newLdapException(ADMIN_LIMIT_EXCEEDED));
                                 } else if (subFilters.size() == 1) {
                                     return subFilters.get(0);
                                 } else {
@@ -183,8 +184,8 @@
     }
 
     @Override
-    Promise<Attribute, ResourceException> getNewLdapAttributes(final Connection connection, final JsonPointer path,
-                                                               final List<Object> newValues) {
+    Promise<Attribute, ResourceException> getNewLdapAttributes(final Connection connection, final Resource resource,
+                                                               final JsonPointer path, final List<Object> newValues) {
         /*
          * For each value use the subordinate mapper to obtain the LDAP primary
          * key, the perform a search for each one to find the corresponding entries.
@@ -195,70 +196,75 @@
         final PromiseImpl<Attribute, ResourceException> promise = PromiseImpl.create();
 
         for (final Object value : newValues) {
-            mapper.create(connection, path, new JsonValue(value)).thenOnResult(new ResultHandler<List<Attribute>>() {
-                @Override
-                public void handleResult(List<Attribute> result) {
-                    Attribute primaryKeyAttribute = null;
-                    for (final Attribute attribute : result) {
-                        if (attribute.getAttributeDescription().equals(primaryKey)) {
-                            primaryKeyAttribute = attribute;
-                            break;
-                        }
-                    }
+            mapper.create(connection, resource, path, new JsonValue(value))
+                  .thenOnResult(new ResultHandler<List<Attribute>>() {
+                      @Override
+                      public void handleResult(List<Attribute> result) {
+                          Attribute primaryKeyAttribute = null;
+                          for (final Attribute attribute : result) {
+                              if (attribute.getAttributeDescription().equals(primaryKey)) {
+                                  primaryKeyAttribute = attribute;
+                                  break;
+                              }
+                          }
 
-                    if (primaryKeyAttribute == null || primaryKeyAttribute.isEmpty()) {
-                        promise.handleException(newBadRequestException(ERR_REFERENCE_FIELD_NO_PRIMARY_KEY.get(path)));
-                    }
+                          if (primaryKeyAttribute == null || primaryKeyAttribute.isEmpty()) {
+                              promise.handleException(newBadRequestException(
+                                      ERR_REFERENCE_FIELD_NO_PRIMARY_KEY.get(path)));
+                              return;
+                          }
 
-                    if (primaryKeyAttribute.size() > 1) {
-                        promise.handleException(
-                                newBadRequestException(ERR_REFERENCE_FIELD_MULTIPLE_PRIMARY_KEYS.get(path)));
-                    }
+                          if (primaryKeyAttribute.size() > 1) {
+                              promise.handleException(
+                                      newBadRequestException(ERR_REFERENCE_FIELD_MULTIPLE_PRIMARY_KEYS.get(path)));
+                              return;
+                          }
 
-                    // Now search for the referenced entry in to get its DN.
-                    final ByteString primaryKeyValue = primaryKeyAttribute.firstValue();
-                    final Filter filter = Filter.equality(primaryKey.toString(), primaryKeyValue);
-                    final SearchRequest search = createSearchRequest(filter);
-                    connection.searchSingleEntryAsync(search)
-                              .thenOnResult(new ResultHandler<SearchResultEntry>() {
-                                  @Override
-                                  public void handleResult(final SearchResultEntry result) {
-                                      synchronized (newLDAPAttribute) {
-                                          newLDAPAttribute.add(result.getName());
-                                      }
-                                      completeIfNecessary();
-                                  }
-                              }).thenOnException(new ExceptionHandler<LdapException>() {
-                                  @Override
-                                  public void handleException(final LdapException error) {
-                                      ResourceException re;
-                                      try {
-                                          throw error;
-                                      } catch (final EntryNotFoundException e) {
-                                          re = newBadRequestException(ERR_REFERENCE_FIELD_DOES_NOT_EXIST.get(
-                                                  primaryKeyValue.toString(), path));
-                                      } catch (final MultipleEntriesFoundException e) {
-                                          re = newBadRequestException(
-                                                  ERR_REFERENCE_FIELD_AMBIGUOUS.get(primaryKeyValue.toString(), path));
-                                      } catch (final LdapException e) {
-                                          re = asResourceException(e);
-                                      }
-                                      exception.compareAndSet(null, re);
-                                      completeIfNecessary();
-                                  }
-                              });
-                }
+                          // Now search for the referenced entry in to get its DN.
+                          final ByteString primaryKeyValue = primaryKeyAttribute.firstValue();
+                          final Filter filter = Filter.equality(primaryKey.toString(), primaryKeyValue);
+                          final SearchRequest search = createSearchRequest(filter);
+                          connection.searchSingleEntryAsync(search)
+                                    .thenOnResult(new ResultHandler<SearchResultEntry>() {
+                                        @Override
+                                        public void handleResult(final SearchResultEntry result) {
+                                            synchronized (newLDAPAttribute) {
+                                                newLDAPAttribute.add(result.getName());
+                                            }
+                                            completeIfNecessary();
+                                        }
+                                    })
+                                    .thenOnException(new ExceptionHandler<LdapException>() {
+                                        @Override
+                                        public void handleException(final LdapException error) {
+                                            ResourceException re;
+                                            try {
+                                                throw error;
+                                            } catch (final EntryNotFoundException e) {
+                                                re = newBadRequestException(
+                                                        ERR_REFERENCE_FIELD_DOES_NOT_EXIST.get(primaryKeyValue, path));
+                                            } catch (final MultipleEntriesFoundException e) {
+                                                re = newBadRequestException(
+                                                        ERR_REFERENCE_FIELD_AMBIGUOUS.get(primaryKeyValue, path));
+                                            } catch (final LdapException e) {
+                                                re = asResourceException(e);
+                                            }
+                                            exception.compareAndSet(null, re);
+                                            completeIfNecessary();
+                                        }
+                                    });
+                      }
 
-                private void completeIfNecessary() {
-                    if (pendingSearches.decrementAndGet() == 0) {
-                        if (exception.get() != null) {
-                            promise.handleException(exception.get());
-                        } else {
-                            promise.handleResult(newLDAPAttribute);
-                        }
-                    }
-                }
-            });
+                      private void completeIfNecessary() {
+                          if (pendingSearches.decrementAndGet() == 0) {
+                              if (exception.get() != null) {
+                                  promise.handleException(exception.get());
+                              } else {
+                                  promise.handleResult(newLDAPAttribute);
+                              }
+                          }
+                      }
+                  });
         }
         return promise;
     }
@@ -268,28 +274,30 @@
         return this;
     }
 
+    @SuppressWarnings("fallthrough")
     @Override
-    Promise<JsonValue, ResourceException> read(final Connection connection, final JsonPointer path, final Entry e) {
-        final Attribute attribute = e.getAttribute(ldapAttributeName);
-        if (attribute == null || attribute.isEmpty()) {
-            return Promises.newResultPromise(null);
-        } else if (attributeIsSingleValued()) {
-            try {
-                final DN dn = attribute.parse().usingSchema(schema).asDN();
-                return readEntry(connection, path, dn);
-            } catch (final Exception ex) {
-                // The LDAP attribute could not be decoded.
-                return Promises.newExceptionPromise(asResourceException(ex));
+    Promise<JsonValue, ResourceException> read(final Connection connection, final Resource resource,
+                                               final JsonPointer path, final Entry e) {
+        final Set<DN> dns = e.parseAttribute(ldapAttributeName).usingSchema(schema).asSetOfDN();
+        switch (dns.size()) {
+        case 0:
+            return newResultPromise(null);
+        case 1:
+            if (attributeIsSingleValued()) {
+                try {
+                    return readEntry(connection, resource, path, dns.iterator().next());
+                } catch (final Exception ex) {
+                    // The LDAP attribute could not be decoded.
+                    return Promises.newExceptionPromise(asResourceException(ex));
+                }
             }
-        } else {
+            // Fall-though: unexpectedly got multiple values. It's probably best to just return them.
+        default:
             try {
-                final Set<DN> dns = attribute.parse().usingSchema(schema).asSetOfDN();
-
                 final List<Promise<JsonValue, ResourceException>> promises = new ArrayList<>(dns.size());
                 for (final DN dn : dns) {
-                    promises.add(readEntry(connection, path, dn));
+                    promises.add(readEntry(connection, resource, path, dn));
                 }
-
                 return Promises.when(promises)
                                .then(new Function<List<JsonValue>, JsonValue, ResourceException>() {
                                    @Override
@@ -322,9 +330,9 @@
     }
 
     private Promise<JsonValue, ResourceException> readEntry(
-            final Connection connection, final JsonPointer path, final DN dn) {
+            final Connection connection, final Resource resource, final JsonPointer path, final DN dn) {
         final Set<String> requestedLDAPAttributes = new LinkedHashSet<>();
-        mapper.getLdapAttributes(connection, path, new JsonPointer(), requestedLDAPAttributes);
+        mapper.getLdapAttributes(path, new JsonPointer(), requestedLDAPAttributes);
 
         final Filter searchFilter = filter != null ? filter : Filter.alwaysTrue();
         final String[] attributes = requestedLDAPAttributes.toArray(new String[requestedLDAPAttributes.size()]);
@@ -335,7 +343,7 @@
                 .thenAsync(new AsyncFunction<SearchResultEntry, JsonValue, ResourceException>() {
                     @Override
                     public Promise<JsonValue, ResourceException> apply(final SearchResultEntry result) {
-                        return mapper.read(connection, path, result);
+                        return mapper.read(connection, resource, path, result);
                     }
                 }, new AsyncFunction<LdapException, JsonValue, ResourceException>() {
                     @Override

--
Gitblit v1.10.0