mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Matthew Swift
04.12.2013 ff499dddfe79aabd9b995f3f4d2a033ae7b2cfa7
OPENDJ-692: Implement delete support

* add MVCC support

1 files deleted
5 files modified
178 ■■■■■ changed files
opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json 11 ●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java 17 ●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java 49 ●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/MVCCStrategy.java 60 ●●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java 37 ●●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/BasicRequestsTest.java 4 ●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json
@@ -136,6 +136,12 @@
            "/users" : {
                "baseDN" : "ou=people,dc=example,dc=com",
                "readOnUpdatePolicy" : "controls",
                "useSubtreeDelete" : true,
                "etagAttribute" : "etag",
                "namingStrategy" : {
                    "strategy" : "clientDNNaming",
                    "dnAttribute" : "uid"
                },
                "additionalLDAPAttributes" : [
                    {
                        "type" : "objectClass",
@@ -147,11 +153,6 @@
                        ]
                    }
                ],
                "namingStrategy" : {
                    "strategy" : "clientDNNaming",
                    "dnAttribute" : "uid"
                },
                "etagAttribute" : "etag",
                "attributes" : {
                    "schemas"     : { "constant" : [ "urn:scim:schemas:core:1.0" ] },
                    "_id"         : { "simple"   : { "ldapAttribute" : "uid", "isSingleValued" : true, "isRequired" : true, "writability" : "createOnly" } },
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java
@@ -23,20 +23,22 @@
 * Common configuration options.
 */
final class Config {
    private final AuthorizationPolicy authzPolicy;
    private final ConnectionFactory factory;
    private final DecodeOptions options;
    private final AuthorizationPolicy authzPolicy;
    private final AuthzIdTemplate proxiedAuthzTemplate;
    private final ReadOnUpdatePolicy readOnUpdatePolicy;
    private final Schema schema;
    private final boolean useSubtreeDelete;
    Config(final ConnectionFactory factory, final ReadOnUpdatePolicy readOnUpdatePolicy,
            final AuthorizationPolicy authzPolicy, final AuthzIdTemplate proxiedAuthzTemplate,
            final Schema schema) {
            final boolean useSubtreeDelete, final Schema schema) {
        this.factory = factory;
        this.readOnUpdatePolicy = readOnUpdatePolicy;
        this.authzPolicy = authzPolicy;
        this.proxiedAuthzTemplate = proxiedAuthzTemplate;
        this.useSubtreeDelete = useSubtreeDelete;
        this.schema = schema;
        this.options = new DecodeOptions().setSchema(schema);
    }
@@ -86,6 +88,17 @@
    }
    /**
     * Returns {@code true} if delete requests should include the subtree delete
     * control.
     *
     * @return {@code true} if delete requests should include the subtree delete
     *         control.
     */
    boolean useSubtreeDelete() {
        return useSubtreeDelete;
    }
    /**
     * Returns the policy which should be used in order to read an entry before
     * it is deleted, or after it is added or modified.
     *
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
@@ -54,6 +54,7 @@
import org.forgerock.json.resource.UncategorizedException;
import org.forgerock.json.resource.UpdateRequest;
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.AttributeDescription;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.DecodeException;
import org.forgerock.opendj.ldap.Entry;
@@ -62,10 +63,12 @@
import org.forgerock.opendj.ldap.Function;
import org.forgerock.opendj.ldap.SearchResultHandler;
import org.forgerock.opendj.ldap.SearchScope;
import org.forgerock.opendj.ldap.controls.AssertionRequestControl;
import org.forgerock.opendj.ldap.controls.PostReadRequestControl;
import org.forgerock.opendj.ldap.controls.PostReadResponseControl;
import org.forgerock.opendj.ldap.controls.PreReadRequestControl;
import org.forgerock.opendj.ldap.controls.PreReadResponseControl;
import org.forgerock.opendj.ldap.controls.SubtreeDeleteRequestControl;
import org.forgerock.opendj.ldap.requests.AddRequest;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.requests.SearchRequest;
@@ -86,17 +89,17 @@
    private final AttributeMapper attributeMapper;
    private final DN baseDN; // TODO: support template variables.
    private final Config config;
    private final MVCCStrategy mvccStrategy;
    private final AttributeDescription etagAttribute;
    private final NameStrategy nameStrategy;
    LDAPCollectionResourceProvider(final DN baseDN, final AttributeMapper mapper,
            final NameStrategy nameStrategy, final MVCCStrategy mvccStrategy, final Config config,
            final List<Attribute> additionalLDAPAttributes) {
            final NameStrategy nameStrategy, final AttributeDescription etagAttribute,
            final Config config, final List<Attribute> additionalLDAPAttributes) {
        this.baseDN = baseDN;
        this.attributeMapper = mapper;
        this.config = config;
        this.nameStrategy = nameStrategy;
        this.mvccStrategy = mvccStrategy;
        this.etagAttribute = etagAttribute;
        this.additionalLDAPAttributes = additionalLDAPAttributes;
    }
@@ -167,8 +170,6 @@
        final Context c = wrap(context);
        final ResultHandler<Resource> h = wrap(c, handler);
        // FIXME: assertion and subtree delete controls.
        // Get connection then perform the search.
        c.run(h, new Runnable() {
            @Override
@@ -186,6 +187,7 @@
                            @Override
                            public void handleResult(final SearchResultEntry entry) {
                                try {
                                // Perform delete operation.
                                final ChangeRecord deleteRequest =
                                        Requests.newDeleteRequest(entry.getName());
@@ -195,8 +197,16 @@
                                    deleteRequest.addControl(PreReadRequestControl.newControl(
                                            false, attributes));
                                }
                                    if (config.useSubtreeDelete()) {
                                        deleteRequest.addControl(SubtreeDeleteRequestControl
                                                .newControl(true));
                                    }
                                    addAssertionControl(deleteRequest, request.getRevision());
                                c.getConnection().applyChangeAsync(deleteRequest, null,
                                        postUpdateHandler(c, h));
                                } catch (final Exception e) {
                                    h.handleError(asResourceException(e));
                                }
                            }
                        });
            }
@@ -260,8 +270,7 @@
                                    }
                                    final String id = nameStrategy.getResourceId(c, entry);
                                    final String revision =
                                            mvccStrategy.getRevisionFromEntry(c, entry);
                                    final String revision = getRevisionFromEntry(entry);
                                    final ResultHandler<JsonValue> mapHandler =
                                            new ResultHandler<JsonValue>() {
                                                @Override
@@ -370,7 +379,7 @@
    private void adaptEntry(final Context c, final Entry entry,
            final ResultHandler<Resource> handler) {
        final String actualResourceId = nameStrategy.getResourceId(c, entry);
        final String revision = mvccStrategy.getRevisionFromEntry(c, entry);
        final String revision = getRevisionFromEntry(entry);
        attributeMapper.toJSON(c, entry, transform(new Function<JsonValue, Resource, Void>() {
            @Override
            public Resource apply(final JsonValue value, final Void p) {
@@ -379,6 +388,20 @@
        }, handler));
    }
    private void addAssertionControl(final ChangeRecord request, final String revision)
            throws NotSupportedException {
        if (revision != null) {
            if (etagAttribute != null) {
                request.addControl(AssertionRequestControl.newControl(true, Filter.equality(
                        etagAttribute.toString(), revision)));
            } else {
                // FIXME: i18n
                throw new NotSupportedException(
                        "Multi-version concurrency control is not supported by this resource");
            }
        }
    }
    private DN getBaseDN(final Context context) {
        return baseDN;
    }
@@ -410,7 +433,9 @@
        // Get the LDAP attributes required by the Etag and name stategies.
        nameStrategy.getLDAPAttributes(c, requestedLDAPAttributes);
        mvccStrategy.getLDAPAttributes(c, requestedLDAPAttributes);
        if (etagAttribute != null) {
            requestedLDAPAttributes.add(etagAttribute.toString());
        }
        return requestedLDAPAttributes.toArray(new String[requestedLDAPAttributes.size()]);
    }
@@ -590,6 +615,10 @@
        queryFilter.accept(visitor, h);
    }
    private String getRevisionFromEntry(final Entry entry) {
        return etagAttribute != null ? entry.parseAttribute(etagAttribute).asString() : null;
    }
    private org.forgerock.opendj.ldap.ResultHandler<Result> postUpdateHandler(final Context c,
            final ResultHandler<Resource> handler) {
        // The handler which will be invoked for the LDAP add result.
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/MVCCStrategy.java
File was deleted
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
@@ -79,12 +79,13 @@
        private AuthorizationPolicy authzPolicy = AuthorizationPolicy.NONE;
        private DN baseDN; // TODO: support template variables.
        private ConnectionFactory factory;
        private MVCCStrategy mvccStrategy;
        private AttributeDescription etagAttribute;
        private NameStrategy nameStrategy;
        private AuthzIdTemplate proxiedAuthzTemplate;
        private ReadOnUpdatePolicy readOnUpdatePolicy = CONTROLS;
        private AttributeMapper rootMapper;
        private Schema schema = Schema.getDefaultSchema();
        private boolean useSubtreeDelete;
        Builder() {
            useEtagAttribute();
@@ -142,8 +143,9 @@
                break;
            }
            return new LDAPCollectionResourceProvider(baseDN, rootMapper, nameStrategy,
                    mvccStrategy, new Config(factory, readOnUpdatePolicy, authzPolicy,
                            proxiedAuthzTemplate, schema), additionalLDAPAttributes);
                    etagAttribute, new Config(factory, readOnUpdatePolicy, authzPolicy,
                            proxiedAuthzTemplate, useSubtreeDelete, schema),
                    additionalLDAPAttributes);
        }
        /**
@@ -194,6 +196,10 @@
                useEtagAttribute(etagAttribute.asString());
            }
            if (configuration.get("useSubtreeDelete").required().asBoolean()) {
                useSubtreeDelete();
            }
            mapper(configureObjectMapper(configuration.get("attributes").required()));
            return this;
@@ -267,7 +273,7 @@
        }
        public Builder useEtagAttribute(final AttributeDescription attribute) {
            this.mvccStrategy = new AttributeMVCCStrategy(attribute);
            this.etagAttribute = attribute;
            return this;
        }
@@ -294,6 +300,11 @@
            return useServerNaming(at(dnAttribute), ad(idAttribute));
        }
        public Builder useSubtreeDelete() {
            this.useSubtreeDelete = true;
            return this;
        }
        private AttributeDescription ad(final String attribute) {
            return AttributeDescription.valueOf(attribute, schema);
        }
@@ -386,24 +397,6 @@
        }
    }
    private static final class AttributeMVCCStrategy extends MVCCStrategy {
        private final AttributeDescription ldapAttribute;
        private AttributeMVCCStrategy(final AttributeDescription ldapAttribute) {
            this.ldapAttribute = ldapAttribute;
        }
        @Override
        void getLDAPAttributes(final Context c, final Set<String> ldapAttributes) {
            ldapAttributes.add(ldapAttribute.toString());
        }
        @Override
        String getRevisionFromEntry(final Context c, final Entry entry) {
            return entry.parseAttribute(ldapAttribute).asString();
        }
    }
    private static final class AttributeNameStrategy extends NameStrategy {
        private final AttributeDescription dnAttribute;
        private final AttributeDescription idAttribute;
opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/BasicRequestsTest.java
@@ -27,9 +27,9 @@
import java.io.IOException;
import org.forgerock.json.resource.ConflictException;
import org.forgerock.json.resource.Connection;
import org.forgerock.json.resource.NotFoundException;
import org.forgerock.json.resource.PreconditionFailedException;
import org.forgerock.json.resource.RequestHandler;
import org.forgerock.json.resource.Resource;
import org.forgerock.json.resource.RootContext;
@@ -79,7 +79,7 @@
        }
    }
    @Test(expectedExceptions = ConflictException.class, enabled = false)
    @Test(expectedExceptions = PreconditionFailedException.class)
    public void testDeleteMVCCNoMatch() throws Exception {
        final RequestHandler handler = newCollection(builder().build());
        final Connection connection = newInternalConnection(handler);