From ff499dddfe79aabd9b995f3f4d2a033ae7b2cfa7 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Wed, 03 Apr 2013 23:12:04 +0000
Subject: [PATCH] OPENDJ-692: Implement delete support
---
opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json | 11 +-
opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/BasicRequestsTest.java | 4
/dev/null | 60 ---------------
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java | 37 +++-----
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java | 69 ++++++++++++-----
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java | 17 +++
6 files changed, 87 insertions(+), 111 deletions(-)
diff --git a/opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json b/opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json
index e3f3547..538842a 100644
--- a/opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json
+++ b/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" } },
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java
index cbabe03..cca57bd 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java
+++ b/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.
*
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
index cc71506..a3c6824 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
+++ b/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,17 +187,26 @@
@Override
public void handleResult(final SearchResultEntry entry) {
- // Perform delete operation.
- final ChangeRecord deleteRequest =
- Requests.newDeleteRequest(entry.getName());
- if (config.readOnUpdatePolicy() == CONTROLS) {
- final String[] attributes =
- getLDAPAttributes(c, request.getFields());
- deleteRequest.addControl(PreReadRequestControl.newControl(
- false, attributes));
+ try {
+ // Perform delete operation.
+ final ChangeRecord deleteRequest =
+ Requests.newDeleteRequest(entry.getName());
+ if (config.readOnUpdatePolicy() == CONTROLS) {
+ final String[] attributes =
+ getLDAPAttributes(c, request.getFields());
+ 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));
}
- c.getConnection().applyChangeAsync(deleteRequest, null,
- postUpdateHandler(c, h));
}
});
}
@@ -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.
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/MVCCStrategy.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/MVCCStrategy.java
deleted file mode 100644
index 5e33383..0000000
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/MVCCStrategy.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * The contents of this file are subject to the terms of the Common Development and
- * Distribution License (the License). You may not use this file except in compliance with the
- * License.
- *
- * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
- * specific language governing permission and limitations under the License.
- *
- * When distributing Covered Software, include this CDDL Header Notice in each file and include
- * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
- * Header, with the fields enclosed by brackets [] replaced by your own identifying
- * information: "Portions copyright [year] [name of copyright owner]".
- *
- * Copyright 2013 ForgeRock AS.
- */
-
-package org.forgerock.opendj.rest2ldap;
-
-import java.util.Set;
-
-import org.forgerock.opendj.ldap.Entry;
-
-/**
- * A multi-version concurrency control strategy is responsible for ensuring that
- * clients can perform atomic updates to LDAP resources.
- */
-abstract class MVCCStrategy {
- /*
- * This interface is an abstract class so that methods can be made package
- * private until API is finalized.
- */
-
- MVCCStrategy() {
- // Nothing to do.
- }
-
- /**
- * Adds the name of any LDAP attribute required by this MVCC strategy to the
- * provided set.
- *
- * @param c
- * The context.
- * @param ldapAttributes
- * The set into which any required LDAP attribute name should be
- * put.
- */
- abstract void getLDAPAttributes(Context c, Set<String> ldapAttributes);
-
- /**
- * Retrieves the revision value (etag) from the provided LDAP entry.
- *
- * @param c
- * The context.
- * @param entry
- * The LDAP entry.
- * @return The revision value.
- */
- abstract String getRevisionFromEntry(Context c, Entry entry);
-
-}
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
index c2837e3..7add0d5 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
+++ b/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;
diff --git a/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/BasicRequestsTest.java b/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/BasicRequestsTest.java
index 68af5c4..28a8851 100644
--- a/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/BasicRequestsTest.java
+++ b/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);
--
Gitblit v1.10.0