From 71178c4013389172080487b47ad407ae211c304b Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Thu, 28 Mar 2013 11:50:36 +0000
Subject: [PATCH] Fix OPENDJ-354: Implement a RequestHandler which provides an in-memory backend
---
opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/util/ConnectionDecorator.java | 10
opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java | 6
opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AbstractConnection.java | 12
opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/controls/PostReadRequestControl.java | 55
opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/AbstractRequestImpl.java | 52 +
opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/FixedConnectionPool.java | 5
opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/controls/PreReadRequestControl.java | 55
opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Connection.java | 31
opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/AbstractUnmodifiableRequest.java | 55
opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/MemoryBackendTestCase.java | 455 +++++++++++++
opendj-sdk/opendj3/opendj-ldap-sdk/src/main/resources/org/forgerock/opendj/ldap/core.properties | 15
opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Modification.java | 5
opendj-sdk/opendj3/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Server.java | 364 ----------
opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/Request.java | 13
opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/responses/AbstractResponseImpl.java | 52
opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Entries.java | 209 ++++++
opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/ErrorResultException.java | 1
opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/responses/AbstractUnmodifiableResponseImpl.java | 52
opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/responses/Response.java | 13
opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/MemoryBackend.java | 461 +++++++++++++
20 files changed, 1,409 insertions(+), 512 deletions(-)
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Server.java b/opendj-sdk/opendj3/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Server.java
index 575f3ae..1837cfe 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Server.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Server.java
@@ -22,61 +22,28 @@
*
*
* Copyright 2009-2010 Sun Microsystems, Inc.
- * Portions copyright 2011-2012 ForgeRock AS
+ * Portions copyright 2011-2013 ForgeRock AS
*/
package org.forgerock.opendj.examples;
-import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
-
import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.IOException;
-import java.io.InputStream;
-import java.util.NavigableMap;
-import java.util.concurrent.ConcurrentSkipListMap;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.net.ssl.SSLContext;
-import org.forgerock.opendj.ldap.CancelledResultException;
import org.forgerock.opendj.ldap.Connections;
-import org.forgerock.opendj.ldap.DN;
-import org.forgerock.opendj.ldap.Entry;
import org.forgerock.opendj.ldap.ErrorResultException;
-import org.forgerock.opendj.ldap.Filter;
-import org.forgerock.opendj.ldap.IntermediateResponseHandler;
import org.forgerock.opendj.ldap.KeyManagers;
import org.forgerock.opendj.ldap.LDAPClientContext;
import org.forgerock.opendj.ldap.LDAPListener;
import org.forgerock.opendj.ldap.LDAPListenerOptions;
-import org.forgerock.opendj.ldap.LinkedHashMapEntry;
-import org.forgerock.opendj.ldap.Matcher;
-import org.forgerock.opendj.ldap.Modification;
-import org.forgerock.opendj.ldap.ModificationType;
-import org.forgerock.opendj.ldap.RequestContext;
-import org.forgerock.opendj.ldap.RequestHandler;
+import org.forgerock.opendj.ldap.MemoryBackend;
import org.forgerock.opendj.ldap.ResultCode;
-import org.forgerock.opendj.ldap.ResultHandler;
import org.forgerock.opendj.ldap.SSLContextBuilder;
-import org.forgerock.opendj.ldap.SearchResultHandler;
-import org.forgerock.opendj.ldap.SearchScope;
import org.forgerock.opendj.ldap.ServerConnection;
import org.forgerock.opendj.ldap.ServerConnectionFactory;
import org.forgerock.opendj.ldap.TrustManagers;
-import org.forgerock.opendj.ldap.requests.AddRequest;
-import org.forgerock.opendj.ldap.requests.BindRequest;
-import org.forgerock.opendj.ldap.requests.CompareRequest;
-import org.forgerock.opendj.ldap.requests.DeleteRequest;
-import org.forgerock.opendj.ldap.requests.ExtendedRequest;
-import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
-import org.forgerock.opendj.ldap.requests.ModifyRequest;
-import org.forgerock.opendj.ldap.requests.SearchRequest;
-import org.forgerock.opendj.ldap.responses.BindResult;
-import org.forgerock.opendj.ldap.responses.CompareResult;
-import org.forgerock.opendj.ldap.responses.ExtendedResult;
-import org.forgerock.opendj.ldap.responses.Responses;
-import org.forgerock.opendj.ldap.responses.Result;
import org.forgerock.opendj.ldif.LDIFEntryReader;
/**
@@ -95,260 +62,6 @@
* </pre>
*/
public final class Server {
- private static final class MemoryBackend implements RequestHandler<RequestContext> {
- private final ConcurrentSkipListMap<DN, Entry> entries;
- private final ReentrantReadWriteLock entryLock = new ReentrantReadWriteLock();
-
- private MemoryBackend(final ConcurrentSkipListMap<DN, Entry> entries) {
- this.entries = entries;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void handleAdd(final RequestContext requestContext, final AddRequest request,
- final IntermediateResponseHandler intermediateResponseHandler,
- final ResultHandler<? super Result> resultHandler) {
- // TODO: controls.
- entryLock.writeLock().lock();
- try {
- DN dn = request.getName();
- if (entries.containsKey(dn)) {
- resultHandler.handleErrorResult(ErrorResultException.newErrorResult(
- ResultCode.ENTRY_ALREADY_EXISTS, "The entry " + dn.toString()
- + " already exists"));
- }
-
- DN parent = dn.parent();
- if (!entries.containsKey(parent)) {
- resultHandler.handleErrorResult(ErrorResultException.newErrorResult(
- ResultCode.NO_SUCH_OBJECT, "The parent entry " + parent.toString()
- + " does not exist"));
- } else {
- entries.put(dn, request);
- resultHandler.handleResult(Responses.newResult(ResultCode.SUCCESS));
- }
- } finally {
- entryLock.writeLock().unlock();
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void handleBind(final RequestContext requestContext, final int version,
- final BindRequest request,
- final IntermediateResponseHandler intermediateResponseHandler,
- final ResultHandler<? super BindResult> resultHandler) {
- if (request.getAuthenticationType() != ((byte) 0x80)) {
- // TODO: SASL authentication not implemented.
- resultHandler.handleErrorResult(newErrorResult(ResultCode.PROTOCOL_ERROR,
- "non-SIMPLE authentication not supported: "
- + request.getAuthenticationType()));
- } else {
- // TODO: always succeed.
- resultHandler.handleResult(Responses.newBindResult(ResultCode.SUCCESS));
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void handleCompare(final RequestContext requestContext,
- final CompareRequest request,
- final IntermediateResponseHandler intermediateResponseHandler,
- final ResultHandler<? super CompareResult> resultHandler) {
- // TODO:
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void handleDelete(final RequestContext requestContext, final DeleteRequest request,
- final IntermediateResponseHandler intermediateResponseHandler,
- final ResultHandler<? super Result> resultHandler) {
- // TODO: controls.
- entryLock.writeLock().lock();
- try {
- // TODO: check for children.
- DN dn = request.getName();
- if (!entries.containsKey(dn)) {
- resultHandler.handleErrorResult(ErrorResultException.newErrorResult(
- ResultCode.NO_SUCH_OBJECT, "The entry " + dn.toString()
- + " does not exist"));
- } else {
- entries.remove(dn);
- resultHandler.handleResult(Responses.newResult(ResultCode.SUCCESS));
- }
- } finally {
- entryLock.writeLock().unlock();
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public <R extends ExtendedResult> void handleExtendedRequest(
- final RequestContext requestContext, final ExtendedRequest<R> request,
- final IntermediateResponseHandler intermediateResponseHandler,
- final ResultHandler<? super R> resultHandler) {
- // TODO: not implemented.
- resultHandler.handleErrorResult(newErrorResult(ResultCode.PROTOCOL_ERROR,
- "Extended request operation not supported"));
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void handleModify(final RequestContext requestContext, final ModifyRequest request,
- final IntermediateResponseHandler intermediateResponseHandler,
- final ResultHandler<? super Result> resultHandler) {
- // TODO: controls.
- // TODO: read lock is not really enough since concurrent updates may
- // still occur to the same entry.
- entryLock.readLock().lock();
- try {
- DN dn = request.getName();
- Entry entry = entries.get(dn);
- if (entry == null) {
- resultHandler.handleErrorResult(ErrorResultException.newErrorResult(
- ResultCode.NO_SUCH_OBJECT, "The entry " + dn.toString()
- + " does not exist"));
- }
-
- Entry newEntry = new LinkedHashMapEntry(entry);
- for (Modification mod : request.getModifications()) {
- ModificationType modType = mod.getModificationType();
- if (modType.equals(ModificationType.ADD)) {
- // TODO: Reject empty attribute and duplicate values.
- newEntry.addAttribute(mod.getAttribute(), null);
- } else if (modType.equals(ModificationType.DELETE)) {
- // TODO: Reject missing values.
- newEntry.removeAttribute(mod.getAttribute(), null);
- } else if (modType.equals(ModificationType.REPLACE)) {
- newEntry.replaceAttribute(mod.getAttribute());
- } else {
- resultHandler.handleErrorResult(newErrorResult(ResultCode.PROTOCOL_ERROR,
- "Modify request contains an unsupported modification type"));
- return;
- }
- }
-
- entries.put(dn, newEntry);
- resultHandler.handleResult(Responses.newResult(ResultCode.SUCCESS));
- } finally {
- entryLock.readLock().unlock();
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void handleModifyDN(final RequestContext requestContext,
- final ModifyDNRequest request,
- final IntermediateResponseHandler intermediateResponseHandler,
- final ResultHandler<? super Result> resultHandler) {
- // TODO: not implemented.
- resultHandler.handleErrorResult(newErrorResult(ResultCode.PROTOCOL_ERROR,
- "ModifyDN request operation not supported"));
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void handleSearch(final RequestContext requestContext, final SearchRequest request,
- final IntermediateResponseHandler intermediateResponseHandler,
- final SearchResultHandler resultHandler) {
- // TODO: controls, limits, etc.
- entryLock.readLock().lock();
- try {
- DN dn = request.getName();
- Entry baseEntry = entries.get(dn);
- if (baseEntry == null) {
- resultHandler.handleErrorResult(ErrorResultException.newErrorResult(
- ResultCode.NO_SUCH_OBJECT, "The entry " + dn.toString()
- + " does not exist"));
- return;
- }
-
- SearchScope scope = request.getScope();
- Filter filter = request.getFilter();
- Matcher matcher = filter.matcher();
-
- if (scope.equals(SearchScope.BASE_OBJECT)) {
- if (matcher.matches(baseEntry).toBoolean()) {
- sendEntry(request, resultHandler, baseEntry);
- }
- } else if (scope.equals(SearchScope.SINGLE_LEVEL)) {
- NavigableMap<DN, Entry> subtree = entries.tailMap(dn, false);
- for (Entry entry : subtree.values()) {
- // Check for cancellation.
- requestContext.checkIfCancelled(false);
-
- DN childDN = entry.getName();
- if (childDN.isChildOf(dn)) {
- if (!matcher.matches(entry).toBoolean()) {
- continue;
- }
-
- if (!sendEntry(request, resultHandler, entry)) {
- // Caller has asked to stop sending results.
- break;
- }
- } else if (!childDN.isSubordinateOrEqualTo(dn)) {
- // The remaining entries will be out of scope.
- break;
- }
- }
- } else if (scope.equals(SearchScope.WHOLE_SUBTREE)) {
- NavigableMap<DN, Entry> subtree = entries.tailMap(dn);
- for (Entry entry : subtree.values()) {
- // Check for cancellation.
- requestContext.checkIfCancelled(false);
-
- DN childDN = entry.getName();
- if (childDN.isSubordinateOrEqualTo(dn)) {
- if (!matcher.matches(entry).toBoolean()) {
- continue;
- }
-
- if (!sendEntry(request, resultHandler, entry)) {
- // Caller has asked to stop sending results.
- break;
- }
- } else {
- // The remaining entries will be out of scope.
- break;
- }
- }
- } else {
- resultHandler.handleErrorResult(newErrorResult(ResultCode.PROTOCOL_ERROR,
- "Search request contains an unsupported search scope"));
- return;
- }
-
- resultHandler.handleResult(Responses.newResult(ResultCode.SUCCESS));
- } catch (CancelledResultException e) {
- resultHandler.handleErrorResult(e);
- } finally {
- entryLock.readLock().unlock();
- }
- }
-
- private boolean sendEntry(SearchRequest request, SearchResultHandler resultHandler,
- Entry entry) {
- // TODO: check filter, strip attributes.
- return resultHandler.handleEntry(Responses.newSearchResultEntry(entry));
- }
- }
/**
* Main method.
@@ -373,8 +86,14 @@
final String certNickname = (args.length == 6) ? args[5] : null;
// Create the memory backend.
- final ConcurrentSkipListMap<DN, Entry> entries = readEntriesFromLDIF(ldifFileName);
- final MemoryBackend backend = new MemoryBackend(entries);
+ final MemoryBackend backend;
+ try {
+ backend = new MemoryBackend(new LDIFEntryReader(new FileInputStream(ldifFileName)));
+ } catch (final IOException e) {
+ System.err.println(e.getMessage());
+ System.exit(ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue());
+ return; // Keep compiler quiet.
+ }
// Create a server connection adapter.
final ServerConnectionFactory<LDAPClientContext, Integer> connectionHandler =
@@ -395,11 +114,13 @@
.toCharArray(), null))).setTrustManager(
TrustManagers.trustAll()).getSSLContext();
- ServerConnectionFactory<LDAPClientContext, Integer> sslWrapper =
+ final ServerConnectionFactory<LDAPClientContext, Integer> sslWrapper =
new ServerConnectionFactory<LDAPClientContext, Integer>() {
+ @Override
public ServerConnection<Integer> handleAccept(
- LDAPClientContext clientContext) throws ErrorResultException {
+ final LDAPClientContext clientContext)
+ throws ErrorResultException {
clientContext.enableTLS(sslContext, null, null, false, false);
return connectionHandler.handleAccept(clientContext);
}
@@ -422,63 +143,6 @@
}
}
- /**
- * Reads the entries from the named LDIF file.
- *
- * @param ldifFileName
- * The name of the LDIF file.
- * @return The entries.
- */
- private static ConcurrentSkipListMap<DN, Entry> readEntriesFromLDIF(final String ldifFileName) {
- final ConcurrentSkipListMap<DN, Entry> entries;
- // Read the LDIF.
- InputStream ldif;
- try {
- ldif = new FileInputStream(ldifFileName);
- } catch (final FileNotFoundException e) {
- System.err.println(e.getMessage());
- System.exit(ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue());
- return null; // Satisfy compiler.
- }
-
- entries = new ConcurrentSkipListMap<DN, Entry>();
- final LDIFEntryReader reader = new LDIFEntryReader(ldif);
- try {
- while (reader.hasNext()) {
- Entry entry = reader.readEntry();
- entries.put(entry.getName(), entry);
- }
- } catch (final IOException e) {
- System.err.println(e.getMessage());
- System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue());
- return null; // Satisfy compiler.
- } finally {
- try {
- reader.close();
- } catch (final IOException ignored) {
- // Ignore.
- }
- }
-
- // Quickly sanity check that every entry (except root entries) have a
- // parent.
- boolean isValid = true;
- for (DN dn : entries.keySet()) {
- if (dn.size() > 1) {
- DN parent = dn.parent();
- if (!entries.containsKey(parent)) {
- System.err
- .println("The entry \"" + dn.toString() + "\" does not have a parent");
- isValid = false;
- }
- }
- }
- if (!isValid) {
- System.exit(1);
- }
- return entries;
- }
-
private Server() {
// Not used.
}
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/util/ConnectionDecorator.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/util/ConnectionDecorator.java
index 88ea33e..e865863 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/util/ConnectionDecorator.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/util/ConnectionDecorator.java
@@ -289,6 +289,16 @@
* The default implementation is to delegate.
*/
@Override
+ public Result deleteSubtree(final String name) throws ErrorResultException {
+ return connection.deleteSubtree(name);
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * The default implementation is to delegate.
+ */
+ @Override
public <R extends ExtendedResult> R extendedRequest(final ExtendedRequest<R> request)
throws ErrorResultException {
return connection.extendedRequest(request);
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AbstractConnection.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AbstractConnection.java
index e898072..167528b 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AbstractConnection.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AbstractConnection.java
@@ -22,7 +22,7 @@
*
*
* Copyright 2009-2010 Sun Microsystems, Inc.
- * Portions copyright 2011-2012 ForgeRock AS
+ * Portions copyright 2011-2013 ForgeRock AS
*/
package org.forgerock.opendj.ldap;
@@ -36,6 +36,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import org.forgerock.opendj.ldap.controls.SubtreeDeleteRequestControl;
import org.forgerock.opendj.ldap.requests.AddRequest;
import org.forgerock.opendj.ldap.requests.DeleteRequest;
import org.forgerock.opendj.ldap.requests.ExtendedRequest;
@@ -363,6 +364,15 @@
* {@inheritDoc}
*/
@Override
+ public Result deleteSubtree(final String name) throws ErrorResultException {
+ return delete(Requests.newDeleteRequest(name).addControl(
+ SubtreeDeleteRequestControl.newControl(true)));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public <R extends ExtendedResult> R extendedRequest(final ExtendedRequest<R> request)
throws ErrorResultException {
return extendedRequest(request, null);
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Connection.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Connection.java
index 64c3c35..0afa53e 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Connection.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Connection.java
@@ -597,6 +597,37 @@
Result delete(String name) throws ErrorResultException;
/**
+ * Deletes the named entry and all of its subordinates from the Directory
+ * Server.
+ * <p>
+ * This method is equivalent to the following code:
+ *
+ * <pre>
+ * DeleteRequest request = new DeleteRequest(name).addControl(
+ * connection.delete(request);
+ * </pre>
+ *
+ * @param name
+ * The distinguished name of the subtree base entry to be
+ * deleted.
+ * @return The result of the operation.
+ * @throws ErrorResultException
+ * If the result code indicates that the request failed for some
+ * reason.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code name} could not be decoded using the default
+ * schema.
+ * @throws UnsupportedOperationException
+ * If this connection does not support delete operations.
+ * @throws IllegalStateException
+ * If this connection has already been closed, i.e. if
+ * {@code isClosed() == true}.
+ * @throws NullPointerException
+ * If {@code name} was {@code null}.
+ */
+ Result deleteSubtree(String name) throws ErrorResultException;
+
+ /**
* Asynchronously deletes an entry from the Directory Server using the
* provided delete request.
*
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Entries.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Entries.java
index de5fcb4..7511b6b 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Entries.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Entries.java
@@ -22,13 +22,21 @@
*
*
* Copyright 2010 Sun Microsystems, Inc.
- * Portions copyright 2011-2012 ForgeRock AS
+ * Portions copyright 2011-2013 ForgeRock AS
*/
package org.forgerock.opendj.ldap;
import static org.forgerock.opendj.ldap.AttributeDescription.objectClass;
+import static org.forgerock.opendj.ldap.CoreMessages.ERR_ENTRY_DUPLICATE_VALUES;
+import static org.forgerock.opendj.ldap.CoreMessages.ERR_ENTRY_INCREMENT_CANNOT_PARSE_AS_INT;
+import static org.forgerock.opendj.ldap.CoreMessages.ERR_ENTRY_INCREMENT_INVALID_VALUE_COUNT;
+import static org.forgerock.opendj.ldap.CoreMessages.ERR_ENTRY_INCREMENT_NO_SUCH_ATTRIBUTE;
+import static org.forgerock.opendj.ldap.CoreMessages.ERR_ENTRY_NO_SUCH_VALUE;
+import static org.forgerock.opendj.ldap.CoreMessages.ERR_ENTRY_UNKNOWN_MODIFICATION_TYPE;
+import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
@@ -37,6 +45,7 @@
import java.util.Set;
import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.opendj.ldap.controls.PermissiveModifyRequestControl;
import org.forgerock.opendj.ldap.requests.ModifyRequest;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.schema.ObjectClass;
@@ -179,14 +188,16 @@
/**
* {@inheritDoc}
*/
- public AttributeParser parseAttribute(AttributeDescription attributeDescription) {
+ @Override
+ public AttributeParser parseAttribute(final AttributeDescription attributeDescription) {
return entry.parseAttribute(attributeDescription);
}
/**
* {@inheritDoc}
*/
- public AttributeParser parseAttribute(String attributeDescription) {
+ @Override
+ public AttributeParser parseAttribute(final String attributeDescription) {
return entry.parseAttribute(attributeDescription);
}
@@ -251,6 +262,13 @@
}
+ private static final Comparator<Entry> COMPARATOR = new Comparator<Entry>() {
+ @Override
+ public int compare(final Entry o1, final Entry o2) {
+ return o1.getName().compareTo(o2.getName());
+ }
+ };
+
private static final Function<Attribute, Attribute, Void> UNMODIFIABLE_ATTRIBUTE_FUNCTION =
new Function<Attribute, Attribute, Void>() {
@@ -261,12 +279,6 @@
};
- private static final Comparator<Entry> COMPARATOR = new Comparator<Entry>() {
- public int compare(Entry o1, Entry o2) {
- return o1.getName().compareTo(o2.getName());
- }
- };
-
/**
* Returns a {@code Comparator} which can be used to compare entries by name
* using the natural order for DN comparisons (parent before children).
@@ -576,6 +588,126 @@
}
/**
+ * Applies the provided modification to an entry. This method implements
+ * "permissive" modify semantics, ignoring attempts to add duplicate values
+ * or attempts to remove values which do not exist.
+ *
+ * @param entry
+ * The entry to be modified.
+ * @param change
+ * The modification to be applied to the entry.
+ * @return A reference to the updated entry.
+ * @throws ErrorResultException
+ * If an error occurred while performing the change such as an
+ * attempt to increment a value which is not a number. The entry
+ * will not have been modified.
+ */
+ public static Entry modifyEntry(final Entry entry, final Modification change)
+ throws ErrorResultException {
+ return modifyEntry(entry, change, null);
+ }
+
+ /**
+ * Applies the provided modification to an entry. This method implements
+ * "permissive" modify semantics, recording attempts to add duplicate values
+ * or attempts to remove values which do not exist in the provided
+ * collection if provided.
+ *
+ * @param entry
+ * The entry to be modified.
+ * @param change
+ * The modification to be applied to the entry.
+ * @param conflictingValues
+ * A collection into which duplicate or missing values will be
+ * added, or {@code null} if conflicting values should not be
+ * saved.
+ * @return A reference to the updated entry.
+ * @throws ErrorResultException
+ * If an error occurred while performing the change such as an
+ * attempt to increment a value which is not a number. The entry
+ * will not have been modified.
+ */
+ public static Entry modifyEntry(final Entry entry, final Modification change,
+ final Collection<? super ByteString> conflictingValues) throws ErrorResultException {
+ final ModificationType modType = change.getModificationType();
+ if (modType.equals(ModificationType.ADD)) {
+ entry.addAttribute(change.getAttribute(), conflictingValues);
+ } else if (modType.equals(ModificationType.DELETE)) {
+ entry.removeAttribute(change.getAttribute(), conflictingValues);
+ } else if (modType.equals(ModificationType.REPLACE)) {
+ entry.replaceAttribute(change.getAttribute());
+ } else if (modType.equals(ModificationType.INCREMENT)) {
+ incrementAttribute(entry, change.getAttribute());
+ } else {
+ throw newErrorResult(ResultCode.UNWILLING_TO_PERFORM,
+ ERR_ENTRY_UNKNOWN_MODIFICATION_TYPE.get(String.valueOf(modType)).toString());
+ }
+ return entry;
+ }
+
+ /**
+ * Applies the provided modification request to an entry. This method will
+ * utilize "permissive" modify semantics if the request contains the
+ * {@link PermissiveModifyRequestControl}.
+ *
+ * @param entry
+ * The entry to be modified.
+ * @param changes
+ * The modification request to be applied to the entry.
+ * @return A reference to the updated entry.
+ * @throws ErrorResultException
+ * If an error occurred while performing the changes such as an
+ * attempt to add duplicate values, remove values which do not
+ * exist, or increment a value which is not a number. The entry
+ * may have been modified.
+ */
+ public static Entry modifyEntry(final Entry entry, final ModifyRequest changes)
+ throws ErrorResultException {
+ final boolean isPermissive = changes.containsControl(PermissiveModifyRequestControl.OID);
+ return modifyEntry0(entry, changes.getModifications(), isPermissive);
+ }
+
+ /**
+ * Applies the provided modifications to an entry using "permissive" modify
+ * semantics.
+ *
+ * @param entry
+ * The entry to be modified.
+ * @param changes
+ * The modification request to be applied to the entry.
+ * @return A reference to the updated entry.
+ * @throws ErrorResultException
+ * If an error occurred while performing the changes such as an
+ * attempt to increment a value which is not a number. The entry
+ * may have been modified.
+ */
+ public static Entry modifyEntryPermissive(final Entry entry,
+ final Collection<Modification> changes) throws ErrorResultException {
+ return modifyEntry0(entry, changes, true);
+ }
+
+ /**
+ * Applies the provided modifications to an entry using "strict" modify
+ * semantics. Attempts to add duplicate values or attempts to remove values
+ * which do not exist will cause the update to fail.
+ *
+ * @param entry
+ * The entry to be modified.
+ * @param changes
+ * The modification request to be applied to the entry.
+ * @return A reference to the updated entry.
+ * @throws ErrorResultException
+ * If an error occurred while performing the changes such as an
+ * attempt to add duplicate values, remove values which do not
+ * exist, or increment a value which is not a number. The entry
+ * may have been modified.
+ */
+ public static Entry modifyEntryStrict(final Entry entry, final Collection<Modification> changes)
+ throws ErrorResultException {
+ return modifyEntry0(entry, changes, false);
+ }
+
+ /**
* Returns a read-only view of {@code entry} and its attributes. Query
* operations on the returned entry and its attributes "read-through" to the
* underlying entry or attribute, and attempts to modify the returned entry
@@ -596,6 +728,65 @@
}
}
+ private static void incrementAttribute(final Entry entry, final Attribute change)
+ throws ErrorResultException {
+ // First parse the change.
+ final AttributeDescription deltaAd = change.getAttributeDescription();
+ if (change.size() != 1) {
+ throw newErrorResult(ResultCode.CONSTRAINT_VIOLATION,
+ ERR_ENTRY_INCREMENT_INVALID_VALUE_COUNT.get(deltaAd.toString()).toString());
+ }
+ final long delta;
+ try {
+ delta = change.parse().asLong();
+ } catch (final Exception e) {
+ throw newErrorResult(ResultCode.CONSTRAINT_VIOLATION,
+ ERR_ENTRY_INCREMENT_CANNOT_PARSE_AS_INT.get(deltaAd.toString()).toString());
+ }
+
+ // Now apply the increment to the attribute.
+ final Attribute oldAttribute = entry.getAttribute(deltaAd);
+ if (oldAttribute == null) {
+ throw newErrorResult(ResultCode.NO_SUCH_ATTRIBUTE,
+ ERR_ENTRY_INCREMENT_NO_SUCH_ATTRIBUTE.get(deltaAd.toString()).toString());
+ }
+
+ // Re-use existing attribute description in case it differs in case, etc.
+ final Attribute newAttribute = new LinkedAttribute(oldAttribute.getAttributeDescription());
+ try {
+ for (final Long value : oldAttribute.parse().asSetOfLong()) {
+ newAttribute.add(value + delta);
+ }
+ } catch (final Exception e) {
+ throw newErrorResult(ResultCode.CONSTRAINT_VIOLATION,
+ ERR_ENTRY_INCREMENT_CANNOT_PARSE_AS_INT.get(deltaAd.toString()).toString());
+ }
+ entry.replaceAttribute(newAttribute);
+ }
+
+ private static Entry modifyEntry0(final Entry entry, final Collection<Modification> changes,
+ final boolean isPermissive) throws ErrorResultException {
+ final Collection<ByteString> conflictingValues =
+ isPermissive ? null : new ArrayList<ByteString>(0);
+ for (final Modification change : changes) {
+ modifyEntry(entry, change, conflictingValues);
+ if (!isPermissive && !conflictingValues.isEmpty()) {
+ if (change.getModificationType().equals(ModificationType.ADD)) {
+ // Duplicate values.
+ throw newErrorResult(ResultCode.ATTRIBUTE_OR_VALUE_EXISTS,
+ ERR_ENTRY_DUPLICATE_VALUES.get(
+ change.getAttribute().getAttributeDescriptionAsString())
+ .toString());
+ } else {
+ // Missing values.
+ throw newErrorResult(ResultCode.NO_SUCH_ATTRIBUTE, ERR_ENTRY_NO_SUCH_VALUE.get(
+ change.getAttribute().getAttributeDescriptionAsString()).toString());
+ }
+ }
+ }
+ return entry;
+ }
+
// Prevent instantiation.
private Entries() {
// Nothing to do.
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/ErrorResultException.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/ErrorResultException.java
index 4444d3b..e9cf922 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/ErrorResultException.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/ErrorResultException.java
@@ -162,6 +162,7 @@
|| rc == ResultCode.CLIENT_SIDE_ENCODING_ERROR) {
return new ConnectionException(result);
} else if (rc == ResultCode.ATTRIBUTE_OR_VALUE_EXISTS
+ || rc == ResultCode.NO_SUCH_ATTRIBUTE
|| rc == ResultCode.CONSTRAINT_VIOLATION || rc == ResultCode.ENTRY_ALREADY_EXISTS
|| rc == ResultCode.INVALID_ATTRIBUTE_SYNTAX || rc == ResultCode.INVALID_DN_SYNTAX
|| rc == ResultCode.NAMING_VIOLATION || rc == ResultCode.NOT_ALLOWED_ON_NONLEAF
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/FixedConnectionPool.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/FixedConnectionPool.java
index bc6cc11..42ad688 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/FixedConnectionPool.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/FixedConnectionPool.java
@@ -309,6 +309,11 @@
}
@Override
+ public Result deleteSubtree(final String name) throws ErrorResultException {
+ return checkState().deleteSubtree(name);
+ }
+
+ @Override
public <R extends ExtendedResult> R extendedRequest(final ExtendedRequest<R> request)
throws ErrorResultException {
return checkState().extendedRequest(request);
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/MemoryBackend.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/MemoryBackend.java
new file mode 100644
index 0000000..92020ff
--- /dev/null
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/MemoryBackend.java
@@ -0,0 +1,461 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ * Copyright 2013 ForgeRock AS.
+ */
+package org.forgerock.opendj.ldap;
+
+import static org.forgerock.opendj.ldap.Attributes.singletonAttribute;
+import static org.forgerock.opendj.ldap.Entries.modifyEntry;
+import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
+import static org.forgerock.opendj.ldap.responses.Responses.newBindResult;
+import static org.forgerock.opendj.ldap.responses.Responses.newCompareResult;
+import static org.forgerock.opendj.ldap.responses.Responses.newResult;
+import static org.forgerock.opendj.ldap.responses.Responses.newSearchResultEntry;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.NavigableMap;
+import java.util.concurrent.ConcurrentSkipListMap;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.forgerock.i18n.LocalizedIllegalArgumentException;
+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.BindRequest;
+import org.forgerock.opendj.ldap.requests.CompareRequest;
+import org.forgerock.opendj.ldap.requests.DeleteRequest;
+import org.forgerock.opendj.ldap.requests.ExtendedRequest;
+import org.forgerock.opendj.ldap.requests.GenericBindRequest;
+import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
+import org.forgerock.opendj.ldap.requests.ModifyRequest;
+import org.forgerock.opendj.ldap.requests.Request;
+import org.forgerock.opendj.ldap.requests.SearchRequest;
+import org.forgerock.opendj.ldap.requests.SimpleBindRequest;
+import org.forgerock.opendj.ldap.responses.BindResult;
+import org.forgerock.opendj.ldap.responses.CompareResult;
+import org.forgerock.opendj.ldap.responses.ExtendedResult;
+import org.forgerock.opendj.ldap.responses.Result;
+import org.forgerock.opendj.ldap.schema.Schema;
+import org.forgerock.opendj.ldif.EntryReader;
+
+/**
+ * A simple in memory back-end which can be used for testing. It is not intended
+ * for production use due to various limitations. The back-end implementations
+ * supports the following:
+ * <ul>
+ * <li>add, bind (simple), compare, delete, modify, and search operations, but
+ * not modifyDN nor extended operations
+ * <li>assertion, pre-, and post- read controls, subtree delete control, and
+ * permissive modify control
+ * <li>thread safety - supports concurrent operations
+ * </ul>
+ * It does not support the following:
+ * <ul>
+ * <li>high performance
+ * <li>secure password storage
+ * <li>schema checking
+ * <li>persistence
+ * <li>indexing
+ * </ul>
+ * This class can be used in conjunction with the factories defined in
+ * {@link Connections} to create simpler servers as well as mock LDAP
+ * connections. For example, to create a mock LDAP connection factory:
+ *
+ * <pre>
+ * MemoryBackend backend = new MemoryBackend();
+ * Connection connection = newInternalConnectionFactory(newServerConnectionFactory(backend), null)
+ * .getConnection();
+ * </pre>
+ */
+public final class MemoryBackend implements RequestHandler<RequestContext> {
+ private final DecodeOptions decodeOptions;
+ private final ConcurrentSkipListMap<DN, Entry> entries = new ConcurrentSkipListMap<DN, Entry>();
+ private final ReentrantReadWriteLock entryLock = new ReentrantReadWriteLock();
+ private final Schema schema;
+
+ /**
+ * Creates a new empty memory backend which will use the default schema.
+ */
+ public MemoryBackend() {
+ this(Schema.getDefaultSchema());
+ }
+
+ /**
+ * Creates a new memory backend which will use the default schema, and will
+ * contain the entries read from the provided entry reader.
+ *
+ * @param reader
+ * The entry reader.
+ * @throws IOException
+ * If an unexpected IO error occurred while reading the entries.
+ */
+ public MemoryBackend(final EntryReader reader) throws IOException {
+ this(Schema.getDefaultSchema(), reader);
+ }
+
+ /**
+ * Creates a new empty memory backend which will use the provided schema.
+ *
+ * @param schema
+ * The schema to use for decoding filters, etc.
+ */
+ public MemoryBackend(final Schema schema) {
+ this.schema = schema;
+ this.decodeOptions = new DecodeOptions().setSchema(schema);
+ }
+
+ /**
+ * Creates a new memory backend which will use the provided schema, and will
+ * contain the entries read from the provided entry reader.
+ *
+ * @param schema
+ * The schema to use for decoding filters, etc.
+ * @param reader
+ * The entry reader.
+ * @throws IOException
+ * If an unexpected IO error occurred while reading the entries.
+ */
+ public MemoryBackend(final Schema schema, final EntryReader reader) throws IOException {
+ this.schema = schema;
+ this.decodeOptions = new DecodeOptions().setSchema(schema);
+ if (reader != null) {
+ try {
+ while (reader.hasNext()) {
+ final Entry entry = reader.readEntry();
+ final DN dn = entry.getName();
+ if (entries.containsKey(dn)) {
+ throw new ErrorResultIOException(newErrorResult(
+ ResultCode.ENTRY_ALREADY_EXISTS, "Attempted to add the entry '"
+ + dn.toString() + "' multiple times"));
+ } else {
+ entries.put(dn, entry);
+ }
+ }
+ } finally {
+ reader.close();
+ }
+ }
+ }
+
+ @Override
+ public void handleAdd(final RequestContext requestContext, final AddRequest request,
+ final IntermediateResponseHandler intermediateResponseHandler,
+ final ResultHandler<? super Result> resultHandler) {
+ entryLock.writeLock().lock();
+ try {
+ final DN dn = request.getName();
+ final DN parent = dn.parent();
+ if (entries.containsKey(dn)) {
+ throw newErrorResult(ResultCode.ENTRY_ALREADY_EXISTS, "The entry '" + dn.toString()
+ + "' already exists");
+ } else if (!entries.containsKey(parent)) {
+ noSuchObject(parent);
+ } else {
+ entries.put(dn, request);
+ resultHandler.handleResult(getResult(request, null, request));
+ }
+ } catch (final ErrorResultException e) {
+ resultHandler.handleErrorResult(e);
+ } finally {
+ entryLock.writeLock().unlock();
+ }
+ }
+
+ @Override
+ public void handleBind(final RequestContext requestContext, final int version,
+ final BindRequest request,
+ final IntermediateResponseHandler intermediateResponseHandler,
+ final ResultHandler<? super BindResult> resultHandler) {
+ entryLock.readLock().lock();
+ try {
+ final DN username = DN.valueOf(request.getName(), schema);
+ final byte[] password;
+ if (request instanceof SimpleBindRequest) {
+ password = ((SimpleBindRequest) request).getPassword();
+ } else if (request instanceof GenericBindRequest
+ && request.getAuthenticationType() == ((byte) 0x80)) {
+ password = ((GenericBindRequest) request).getAuthenticationValue();
+ } else {
+ throw newErrorResult(ResultCode.PROTOCOL_ERROR,
+ "non-SIMPLE authentication not supported: "
+ + request.getAuthenticationType());
+ }
+ final Entry entry = getRequiredEntry(null, username);
+ if (entry.containsAttribute("userPassword", password)) {
+ resultHandler.handleResult(getBindResult(request, entry, entry));
+ } else {
+ throw newErrorResult(ResultCode.INVALID_CREDENTIALS, "Wrong password");
+ }
+ } catch (final LocalizedIllegalArgumentException e) {
+ resultHandler.handleErrorResult(newErrorResult(ResultCode.PROTOCOL_ERROR, e));
+ } catch (final EntryNotFoundException e) {
+ /*
+ * Usually you would not include a diagnostic message, but we'll add
+ * one here because the memory back-end is not intended for
+ * production use.
+ */
+ resultHandler.handleErrorResult(newErrorResult(ResultCode.INVALID_CREDENTIALS,
+ "Unknown user"));
+ } catch (final ErrorResultException e) {
+ resultHandler.handleErrorResult(e);
+ } finally {
+ entryLock.readLock().unlock();
+ }
+ }
+
+ @Override
+ public void handleCompare(final RequestContext requestContext, final CompareRequest request,
+ final IntermediateResponseHandler intermediateResponseHandler,
+ final ResultHandler<? super CompareResult> resultHandler) {
+ entryLock.readLock().lock();
+ try {
+ final DN dn = request.getName();
+ final Entry entry = getRequiredEntry(request, dn);
+ final Attribute assertion =
+ singletonAttribute(request.getAttributeDescription(), request
+ .getAssertionValue());
+ resultHandler.handleResult(getCompareResult(request, entry, entry.containsAttribute(
+ assertion, null)));
+ } catch (final ErrorResultException e) {
+ resultHandler.handleErrorResult(e);
+ } finally {
+ entryLock.readLock().unlock();
+ }
+ }
+
+ @Override
+ public void handleDelete(final RequestContext requestContext, final DeleteRequest request,
+ final IntermediateResponseHandler intermediateResponseHandler,
+ final ResultHandler<? super Result> resultHandler) {
+ entryLock.writeLock().lock();
+ try {
+ final DN dn = request.getName();
+ final Entry entry = getRequiredEntry(request, dn);
+ if (request.getControl(SubtreeDeleteRequestControl.DECODER, decodeOptions) != null) {
+ // Subtree delete.
+ entries.subMap(dn, dn.child(RDN.maxValue())).clear();
+ } else {
+ // Must be leaf.
+ final DN next = entries.higherKey(dn);
+ if (next == null || !next.isChildOf(dn)) {
+ entries.remove(dn);
+ } else {
+ throw newErrorResult(ResultCode.NOT_ALLOWED_ON_NONLEAF);
+ }
+ }
+ resultHandler.handleResult(getResult(request, entry, null));
+ } catch (final DecodeException e) {
+ resultHandler.handleErrorResult(newErrorResult(ResultCode.PROTOCOL_ERROR, e));
+ } catch (final ErrorResultException e) {
+ resultHandler.handleErrorResult(e);
+ } finally {
+ entryLock.writeLock().unlock();
+ }
+ }
+
+ @Override
+ public <R extends ExtendedResult> void handleExtendedRequest(
+ final RequestContext requestContext, final ExtendedRequest<R> request,
+ final IntermediateResponseHandler intermediateResponseHandler,
+ final ResultHandler<? super R> resultHandler) {
+ resultHandler.handleErrorResult(newErrorResult(ResultCode.UNWILLING_TO_PERFORM,
+ "Extended request operation not supported"));
+ }
+
+ @Override
+ public void handleModify(final RequestContext requestContext, final ModifyRequest request,
+ final IntermediateResponseHandler intermediateResponseHandler,
+ final ResultHandler<? super Result> resultHandler) {
+ entryLock.writeLock().lock();
+ try {
+ final DN dn = request.getName();
+ final Entry entry = getRequiredEntry(request, dn);
+ final Entry newEntry = new LinkedHashMapEntry(entry);
+ entries.put(dn, modifyEntry(newEntry, request));
+ resultHandler.handleResult(getResult(request, entry, newEntry));
+ } catch (final ErrorResultException e) {
+ resultHandler.handleErrorResult(e);
+ } finally {
+ entryLock.writeLock().unlock();
+ }
+ }
+
+ @Override
+ public void handleModifyDN(final RequestContext requestContext, final ModifyDNRequest request,
+ final IntermediateResponseHandler intermediateResponseHandler,
+ final ResultHandler<? super Result> resultHandler) {
+ resultHandler.handleErrorResult(newErrorResult(ResultCode.UNWILLING_TO_PERFORM,
+ "ModifyDN request operation not supported"));
+ }
+
+ @Override
+ public void handleSearch(final RequestContext requestContext, final SearchRequest request,
+ final IntermediateResponseHandler intermediateResponseHandler,
+ final SearchResultHandler resultHandler) {
+ entryLock.readLock().lock();
+ try {
+ final DN dn = request.getName();
+ final Entry baseEntry = getRequiredEntry(request, dn);
+ final SearchScope scope = request.getScope();
+ final Filter filter = request.getFilter();
+ final Matcher matcher = filter.matcher(schema);
+
+ if (scope.equals(SearchScope.BASE_OBJECT)) {
+ if (matcher.matches(baseEntry).toBoolean()) {
+ sendEntry(request, resultHandler, baseEntry);
+ }
+ } else if (scope.equals(SearchScope.SINGLE_LEVEL)) {
+ final NavigableMap<DN, Entry> subtree =
+ entries.subMap(dn, dn.child(RDN.maxValue()));
+ for (final Entry entry : subtree.values()) {
+ // Check for cancellation.
+ requestContext.checkIfCancelled(false);
+ final DN childDN = entry.getName();
+ if (childDN.isChildOf(dn)) {
+ if (matcher.matches(entry).toBoolean()
+ && !sendEntry(request, resultHandler, entry)) {
+ // Caller has asked to stop sending results.
+ break;
+ }
+ }
+ }
+ } else if (scope.equals(SearchScope.WHOLE_SUBTREE)) {
+ final NavigableMap<DN, Entry> subtree =
+ entries.subMap(dn, dn.child(RDN.maxValue()));
+ for (final Entry entry : subtree.values()) {
+ // Check for cancellation.
+ requestContext.checkIfCancelled(false);
+ if (matcher.matches(entry).toBoolean()
+ && !sendEntry(request, resultHandler, entry)) {
+ // Caller has asked to stop sending results.
+ break;
+ }
+ }
+ } else {
+ throw newErrorResult(ResultCode.PROTOCOL_ERROR,
+ "Search request contains an unsupported search scope");
+ }
+ resultHandler.handleResult(newResult(ResultCode.SUCCESS));
+ } catch (final ErrorResultException e) {
+ resultHandler.handleErrorResult(e);
+ } finally {
+ entryLock.readLock().unlock();
+ }
+ }
+
+ private <R extends Result> R addResultControls(final Request request, final Entry before,
+ final Entry after, final R result) throws ErrorResultException {
+ try {
+ // Add pre-read response control if requested.
+ final PreReadRequestControl preRead =
+ request.getControl(PreReadRequestControl.DECODER, decodeOptions);
+ if (preRead != null) {
+ if (preRead.isCritical() && before == null) {
+ throw newErrorResult(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION);
+ } else {
+ result.addControl(PreReadResponseControl.newControl(filter(before, preRead
+ .getAttributes())));
+ }
+ }
+
+ // Add post-read response control if requested.
+ final PostReadRequestControl postRead =
+ request.getControl(PostReadRequestControl.DECODER, decodeOptions);
+ if (postRead != null) {
+ if (postRead.isCritical() && after == null) {
+ throw newErrorResult(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION);
+ } else {
+ result.addControl(PostReadResponseControl.newControl(filter(after, postRead
+ .getAttributes())));
+ }
+ }
+ return result;
+ } catch (final DecodeException e) {
+ throw newErrorResult(ResultCode.PROTOCOL_ERROR, e);
+ }
+ }
+
+ private Entry filter(final Entry entry, final Collection<String> attributes) {
+ // FIXME: attribute filtering not supported yet.
+ return entry;
+ }
+
+ private BindResult getBindResult(final BindRequest request, final Entry before,
+ final Entry after) throws ErrorResultException {
+ return addResultControls(request, before, after, newBindResult(ResultCode.SUCCESS));
+ }
+
+ private CompareResult getCompareResult(final CompareRequest request, final Entry entry,
+ final boolean compareResult) throws ErrorResultException {
+ return addResultControls(
+ request,
+ entry,
+ entry,
+ newCompareResult(compareResult ? ResultCode.COMPARE_TRUE : ResultCode.COMPARE_FALSE));
+ }
+
+ private Entry getRequiredEntry(final Request request, final DN dn) throws ErrorResultException {
+ final Entry entry = entries.get(dn);
+ if (entry == null) {
+ noSuchObject(dn);
+ } else if (request != null) {
+ AssertionRequestControl control;
+ try {
+ control = request.getControl(AssertionRequestControl.DECODER, decodeOptions);
+ } catch (final DecodeException e) {
+ throw newErrorResult(ResultCode.PROTOCOL_ERROR, e);
+ }
+ if (control != null) {
+ final Filter filter = control.getFilter();
+ final Matcher matcher = filter.matcher(schema);
+ if (!matcher.matches(entry).toBoolean()) {
+ throw newErrorResult(ResultCode.ASSERTION_FAILED, "The filter '"
+ + filter.toString() + "' did not match the entry '"
+ + entry.getName().toString() + "'");
+ }
+ }
+ }
+ return entry;
+ }
+
+ private Result getResult(final Request request, final Entry before, final Entry after)
+ throws ErrorResultException {
+ return addResultControls(request, before, after, newResult(ResultCode.SUCCESS));
+ }
+
+ private void noSuchObject(final DN dn) throws ErrorResultException {
+ throw newErrorResult(ResultCode.NO_SUCH_OBJECT, "The entry '" + dn.toString()
+ + "' does not exist");
+ }
+
+ private boolean sendEntry(final SearchRequest request, final SearchResultHandler resultHandler,
+ final Entry entry) {
+ return resultHandler
+ .handleEntry(newSearchResultEntry(filter(entry, request.getAttributes())));
+ }
+}
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Modification.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Modification.java
index 1e05070..45974ae 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Modification.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Modification.java
@@ -33,7 +33,6 @@
*/
public final class Modification {
private final ModificationType modificationType;
-
private final Attribute attribute;
/**
@@ -44,7 +43,8 @@
* fully immutable:
*
* <pre>
- * Modification change = new Modification(modificationType, Types.unmodifiableAttribute(attribute));
+ * Modification change = new Modification(modificationType, Attributes
+ * .unmodifiableAttribute(attribute));
* </pre>
*
* @param modificationType
@@ -54,7 +54,6 @@
*/
public Modification(final ModificationType modificationType, final Attribute attribute) {
Validator.ensureNotNull(modificationType, attribute);
-
this.modificationType = modificationType;
this.attribute = attribute;
}
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/controls/PostReadRequestControl.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/controls/PostReadRequestControl.java
index 23f6edd..2c65bee 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/controls/PostReadRequestControl.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/controls/PostReadRequestControl.java
@@ -22,21 +22,24 @@
*
*
* Copyright 2009 Sun Microsystems, Inc.
- * Portions copyright 2012 ForgeRock AS.
+ * Portions copyright 2012-2013 ForgeRock AS.
*/
package org.forgerock.opendj.ldap.controls;
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static java.util.Collections.unmodifiableList;
import static org.forgerock.opendj.ldap.CoreMessages.ERR_POSTREADREQ_CANNOT_DECODE_VALUE;
import static org.forgerock.opendj.ldap.CoreMessages.ERR_POSTREADREQ_NO_CONTROL_VALUE;
import static org.forgerock.opendj.ldap.CoreMessages.ERR_POSTREAD_CONTROL_BAD_OID;
import java.io.IOException;
-import java.util.Arrays;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.LinkedHashSet;
-import java.util.Set;
+import java.util.List;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.asn1.ASN1;
@@ -88,16 +91,16 @@
*/
public static final String OID = "1.3.6.1.1.13.2";
- // The set of raw attributes to return in the entry.
- private final Set<String> attributes;
+ // The list of raw attributes to return in the entry.
+ private final List<String> attributes;
private final boolean isCritical;
private static final PostReadRequestControl CRITICAL_EMPTY_INSTANCE =
- new PostReadRequestControl(true, Collections.<String> emptySet());
+ new PostReadRequestControl(true, Collections.<String> emptyList());
private static final PostReadRequestControl NONCRITICAL_EMPTY_INSTANCE =
- new PostReadRequestControl(false, Collections.<String> emptySet());
+ new PostReadRequestControl(false, Collections.<String> emptyList());
/**
* A decoder which can be used for decoding the post-read request control.
@@ -126,23 +129,23 @@
}
final ASN1Reader reader = ASN1.getReader(control.getValue());
- Set<String> attributes;
+ List<String> attributes;
try {
reader.readStartSequence();
if (reader.hasNextElement()) {
final String firstAttribute = reader.readOctetStringAsString();
if (reader.hasNextElement()) {
- attributes = new LinkedHashSet<String>();
+ attributes = new ArrayList<String>();
attributes.add(firstAttribute);
do {
attributes.add(reader.readOctetStringAsString());
} while (reader.hasNextElement());
- attributes = Collections.unmodifiableSet(attributes);
+ attributes = unmodifiableList(attributes);
} else {
- attributes = Collections.singleton(firstAttribute);
+ attributes = singletonList(firstAttribute);
}
} else {
- attributes = Collections.emptySet();
+ attributes = emptyList();
}
reader.readEndSequence();
} catch (final Exception ae) {
@@ -190,11 +193,11 @@
if (attributes.isEmpty()) {
return isCritical ? CRITICAL_EMPTY_INSTANCE : NONCRITICAL_EMPTY_INSTANCE;
} else if (attributes.size() == 1) {
- return new PostReadRequestControl(isCritical, Collections.singleton(attributes
- .iterator().next()));
+ return new PostReadRequestControl(isCritical, singletonList(attributes.iterator()
+ .next()));
} else {
- final Set<String> attributeSet = new LinkedHashSet<String>(attributes);
- return new PostReadRequestControl(isCritical, Collections.unmodifiableSet(attributeSet));
+ return new PostReadRequestControl(isCritical, unmodifiableList(new ArrayList<String>(
+ attributes)));
}
}
@@ -221,28 +224,28 @@
if (attributes.length == 0) {
return isCritical ? CRITICAL_EMPTY_INSTANCE : NONCRITICAL_EMPTY_INSTANCE;
} else if (attributes.length == 1) {
- return new PostReadRequestControl(isCritical, Collections.singleton(attributes[0]));
+ return new PostReadRequestControl(isCritical, singletonList(attributes[0]));
} else {
- final Set<String> attributeSet = new LinkedHashSet<String>(Arrays.asList(attributes));
- return new PostReadRequestControl(isCritical, Collections.unmodifiableSet(attributeSet));
+ return new PostReadRequestControl(isCritical, unmodifiableList(new ArrayList<String>(
+ asList(attributes))));
}
}
- private PostReadRequestControl(final boolean isCritical, final Set<String> attributes) {
+ private PostReadRequestControl(final boolean isCritical, final List<String> attributes) {
this.isCritical = isCritical;
this.attributes = attributes;
}
/**
- * Returns an unmodifiable set containing the names of attributes to be
+ * Returns an unmodifiable list containing the names of attributes to be
* included with the response control. Attributes that are sub-types of
- * listed attributes are implicitly included. The returned set may be empty,
- * indicating that all user attributes should be returned.
+ * listed attributes are implicitly included. The returned list may be
+ * empty, indicating that all user attributes should be returned.
*
- * @return An unmodifiable set containing the names of attributes to be
+ * @return An unmodifiable list containing the names of attributes to be
* included with the response control.
*/
- public Set<String> getAttributes() {
+ public List<String> getAttributes() {
return attributes;
}
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/controls/PreReadRequestControl.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/controls/PreReadRequestControl.java
index bc00844..c2325e5 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/controls/PreReadRequestControl.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/controls/PreReadRequestControl.java
@@ -22,21 +22,24 @@
*
*
* Copyright 2009 Sun Microsystems, Inc.
- * Portions copyright 2012 ForgeRock AS.
+ * Portions copyright 2012-2013 ForgeRock AS.
*/
package org.forgerock.opendj.ldap.controls;
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static java.util.Collections.unmodifiableList;
import static org.forgerock.opendj.ldap.CoreMessages.ERR_PREREADREQ_CANNOT_DECODE_VALUE;
import static org.forgerock.opendj.ldap.CoreMessages.ERR_PREREADREQ_NO_CONTROL_VALUE;
import static org.forgerock.opendj.ldap.CoreMessages.ERR_PREREAD_CONTROL_BAD_OID;
import java.io.IOException;
-import java.util.Arrays;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.LinkedHashSet;
-import java.util.Set;
+import java.util.List;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.asn1.ASN1;
@@ -87,16 +90,16 @@
*/
public static final String OID = "1.3.6.1.1.13.1";
- // The set of raw attributes to return in the entry.
- private final Set<String> attributes;
+ // The list of raw attributes to return in the entry.
+ private final List<String> attributes;
private final boolean isCritical;
private static final PreReadRequestControl CRITICAL_EMPTY_INSTANCE = new PreReadRequestControl(
- true, Collections.<String> emptySet());
+ true, Collections.<String> emptyList());
private static final PreReadRequestControl NONCRITICAL_EMPTY_INSTANCE =
- new PreReadRequestControl(false, Collections.<String> emptySet());
+ new PreReadRequestControl(false, Collections.<String> emptyList());
/**
* A decoder which can be used for decoding the pre-read request control.
@@ -125,23 +128,23 @@
}
final ASN1Reader reader = ASN1.getReader(control.getValue());
- Set<String> attributes;
+ List<String> attributes;
try {
reader.readStartSequence();
if (reader.hasNextElement()) {
final String firstAttribute = reader.readOctetStringAsString();
if (reader.hasNextElement()) {
- attributes = new LinkedHashSet<String>();
+ attributes = new ArrayList<String>();
attributes.add(firstAttribute);
do {
attributes.add(reader.readOctetStringAsString());
} while (reader.hasNextElement());
- attributes = Collections.unmodifiableSet(attributes);
+ attributes = unmodifiableList(attributes);
} else {
- attributes = Collections.singleton(firstAttribute);
+ attributes = singletonList(firstAttribute);
}
} else {
- attributes = Collections.emptySet();
+ attributes = emptyList();
}
reader.readEndSequence();
} catch (final Exception ae) {
@@ -189,11 +192,11 @@
if (attributes.isEmpty()) {
return isCritical ? CRITICAL_EMPTY_INSTANCE : NONCRITICAL_EMPTY_INSTANCE;
} else if (attributes.size() == 1) {
- return new PreReadRequestControl(isCritical, Collections.singleton(attributes
- .iterator().next()));
+ return new PreReadRequestControl(isCritical,
+ singletonList(attributes.iterator().next()));
} else {
- final Set<String> attributeSet = new LinkedHashSet<String>(attributes);
- return new PreReadRequestControl(isCritical, Collections.unmodifiableSet(attributeSet));
+ return new PreReadRequestControl(isCritical, unmodifiableList(new ArrayList<String>(
+ attributes)));
}
}
@@ -220,28 +223,28 @@
if (attributes.length == 0) {
return isCritical ? CRITICAL_EMPTY_INSTANCE : NONCRITICAL_EMPTY_INSTANCE;
} else if (attributes.length == 1) {
- return new PreReadRequestControl(isCritical, Collections.singleton(attributes[0]));
+ return new PreReadRequestControl(isCritical, singletonList(attributes[0]));
} else {
- final Set<String> attributeSet = new LinkedHashSet<String>(Arrays.asList(attributes));
- return new PreReadRequestControl(isCritical, Collections.unmodifiableSet(attributeSet));
+ return new PreReadRequestControl(isCritical, unmodifiableList(new ArrayList<String>(
+ asList(attributes))));
}
}
- private PreReadRequestControl(final boolean isCritical, final Set<String> attributes) {
+ private PreReadRequestControl(final boolean isCritical, final List<String> attributes) {
this.isCritical = isCritical;
this.attributes = attributes;
}
/**
- * Returns an unmodifiable set containing the names of attributes to be
+ * Returns an unmodifiable list containing the names of attributes to be
* included with the response control. Attributes that are sub-types of
- * listed attributes are implicitly included. The returned set may be empty,
- * indicating that all user attributes should be returned.
+ * listed attributes are implicitly included. The returned list may be
+ * empty, indicating that all user attributes should be returned.
*
- * @return An unmodifiable set containing the names of attributes to be
+ * @return An unmodifiable list containing the names of attributes to be
* included with the response control.
*/
- public Set<String> getAttributes() {
+ public List<String> getAttributes() {
return attributes;
}
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/AbstractRequestImpl.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/AbstractRequestImpl.java
index 90f8fd5..f13b118 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/AbstractRequestImpl.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/AbstractRequestImpl.java
@@ -22,7 +22,7 @@
*
*
* Copyright 2010 Sun Microsystems, Inc.
- * Portions copyright 2012 ForgeRock AS.
+ * Portions copyright 2012-2013 ForgeRock AS.
*/
package org.forgerock.opendj.ldap.requests;
@@ -45,6 +45,20 @@
* The type of request.
*/
abstract class AbstractRequestImpl<R extends Request> implements Request {
+
+ // Used by unmodifiable implementations as well.
+ static Control getControl(final List<Control> controls, final String oid) {
+ // Avoid creating an iterator if possible.
+ if (!controls.isEmpty()) {
+ for (final Control control : controls) {
+ if (control.getOID().equals(oid)) {
+ return control;
+ }
+ }
+ }
+ return null;
+ }
+
private final List<Control> controls = new LinkedList<Control>();
/**
@@ -63,9 +77,9 @@
* @throws NullPointerException
* If {@code request} was {@code null} .
*/
- AbstractRequestImpl(Request request) {
+ AbstractRequestImpl(final Request request) {
Validator.ensureNotNull(request);
- for (Control control : request.getControls()) {
+ for (final Control control : request.getControls()) {
// Create defensive copy.
controls.add(GenericControl.newControl(control));
}
@@ -74,6 +88,7 @@
/**
* {@inheritDoc}
*/
+ @Override
public final R addControl(final Control control) {
Validator.ensureNotNull(control);
controls.add(control);
@@ -83,27 +98,26 @@
/**
* {@inheritDoc}
*/
- public final <C extends Control> C getControl(final ControlDecoder<C> decoder,
- final DecodeOptions options) throws DecodeException {
- Validator.ensureNotNull(decoder, options);
-
- // Avoid creating an iterator if possible.
- if (controls.isEmpty()) {
- return null;
- }
-
- for (final Control control : controls) {
- if (control.getOID().equals(decoder.getOID())) {
- return decoder.decodeControl(control, options);
- }
- }
-
- return null;
+ @Override
+ public boolean containsControl(final String oid) {
+ return getControl(controls, oid) != null;
}
/**
* {@inheritDoc}
*/
+ @Override
+ public final <C extends Control> C getControl(final ControlDecoder<C> decoder,
+ final DecodeOptions options) throws DecodeException {
+ Validator.ensureNotNull(decoder, options);
+ final Control control = getControl(controls, decoder.getOID());
+ return control != null ? decoder.decodeControl(control, options) : null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public final List<Control> getControls() {
return controls;
}
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/AbstractUnmodifiableRequest.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/AbstractUnmodifiableRequest.java
index f62e14a..4586e85 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/AbstractUnmodifiableRequest.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/AbstractUnmodifiableRequest.java
@@ -22,7 +22,7 @@
*
*
* Copyright 2010 Sun Microsystems, Inc.
- * Portions copyright 2012 ForgeRock AS.
+ * Portions copyright 2012-2013 ForgeRock AS.
*/
package org.forgerock.opendj.ldap.requests;
@@ -73,38 +73,38 @@
* {@inheritDoc}
*/
@Override
+ public boolean containsControl(final String oid) {
+ return impl.containsControl(oid);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public final <C extends Control> C getControl(final ControlDecoder<C> decoder,
final DecodeOptions options) throws DecodeException {
Validator.ensureNotNull(decoder, options);
final List<Control> controls = impl.getControls();
-
- // Avoid creating an iterator if possible.
- if (controls.isEmpty()) {
+ final Control control = AbstractRequestImpl.getControl(controls, decoder.getOID());
+ if (control != null) {
+ // Got a match. Return a defensive copy only if necessary.
+ final C decodedControl = decoder.decodeControl(control, options);
+ if (decodedControl != control) {
+ // This was not the original control so return it
+ // immediately.
+ return decodedControl;
+ } else if (decodedControl instanceof GenericControl) {
+ // Generic controls are immutable, so return it immediately.
+ return decodedControl;
+ } else {
+ // Re-decode to get defensive copy.
+ final GenericControl genericControl = GenericControl.newControl(control);
+ return decoder.decodeControl(genericControl, options);
+ }
+ } else {
return null;
}
-
- for (final Control control : controls) {
- if (control.getOID().equals(decoder.getOID())) {
- // Got a match. Return a defensive copy only if necessary.
- final C decodedControl = decoder.decodeControl(control, options);
-
- if (decodedControl != control) {
- // This was not the original control so return it
- // immediately.
- return decodedControl;
- } else if (decodedControl instanceof GenericControl) {
- // Generic controls are immutable, so return it immediately.
- return decodedControl;
- } else {
- // Re-decode to get defensive copy.
- final GenericControl genericControl = GenericControl.newControl(control);
- return decoder.decodeControl(genericControl, options);
- }
- }
- }
-
- return null;
}
/**
@@ -113,8 +113,7 @@
@Override
public final List<Control> getControls() {
// We need to make all controls unmodifiable as well, which implies
- // making
- // defensive copies where necessary.
+ // making defensive copies where necessary.
final Function<Control, Control, Void> function = new Function<Control, Control, Void>() {
@Override
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/Request.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/Request.java
index 668f552..908720f 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/Request.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/Request.java
@@ -22,7 +22,7 @@
*
*
* Copyright 2009-2010 Sun Microsystems, Inc.
- * Portions copyright 2012 ForgeRock AS.
+ * Portions copyright 2012-2013 ForgeRock AS.
*/
package org.forgerock.opendj.ldap.requests;
@@ -54,6 +54,17 @@
Request addControl(Control control);
/**
+ * Returns {@code true} if this request contains the specified request
+ * control.
+ *
+ * @param oid
+ * The numeric OID of the request control.
+ * @return {@code true} if this request contains the specified request
+ * control.
+ */
+ boolean containsControl(String oid);
+
+ /**
* Decodes and returns the first control in this request having an OID
* corresponding to the provided control decoder.
*
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/responses/AbstractResponseImpl.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/responses/AbstractResponseImpl.java
index 96195d0..04409f0 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/responses/AbstractResponseImpl.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/responses/AbstractResponseImpl.java
@@ -22,7 +22,7 @@
*
*
* Copyright 2010 Sun Microsystems, Inc.
- * Portions copyright 2012 ForgeRock AS.
+ * Portions copyright 2012-2013 ForgeRock AS.
*/
package org.forgerock.opendj.ldap.responses;
@@ -45,6 +45,19 @@
* The type of response.
*/
abstract class AbstractResponseImpl<S extends Response> implements Response {
+ // Used by unmodifiable implementations as well.
+ static Control getControl(final List<Control> controls, final String oid) {
+ // Avoid creating an iterator if possible.
+ if (!controls.isEmpty()) {
+ for (final Control control : controls) {
+ if (control.getOID().equals(oid)) {
+ return control;
+ }
+ }
+ }
+ return null;
+ }
+
private final List<Control> controls = new LinkedList<Control>();
/**
@@ -63,9 +76,9 @@
* @throws NullPointerException
* If {@code response} was {@code null} .
*/
- AbstractResponseImpl(Response response) {
+ AbstractResponseImpl(final Response response) {
Validator.ensureNotNull(response);
- for (Control control : response.getControls()) {
+ for (final Control control : response.getControls()) {
// Create defensive copy.
controls.add(GenericControl.newControl(control));
}
@@ -74,6 +87,7 @@
/**
* {@inheritDoc}
*/
+ @Override
public final S addControl(final Control control) {
Validator.ensureNotNull(control);
controls.add(control);
@@ -83,27 +97,26 @@
/**
* {@inheritDoc}
*/
- public final <C extends Control> C getControl(final ControlDecoder<C> decoder,
- final DecodeOptions options) throws DecodeException {
- Validator.ensureNotNull(decoder, options);
-
- // Avoid creating an iterator if possible.
- if (controls.isEmpty()) {
- return null;
- }
-
- for (final Control control : controls) {
- if (control.getOID().equals(decoder.getOID())) {
- return decoder.decodeControl(control, options);
- }
- }
-
- return null;
+ @Override
+ public boolean containsControl(final String oid) {
+ return getControl(controls, oid) != null;
}
/**
* {@inheritDoc}
*/
+ @Override
+ public final <C extends Control> C getControl(final ControlDecoder<C> decoder,
+ final DecodeOptions options) throws DecodeException {
+ Validator.ensureNotNull(decoder, options);
+ final Control control = getControl(controls, decoder.getOID());
+ return control != null ? decoder.decodeControl(control, options) : null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public final List<Control> getControls() {
return controls;
}
@@ -112,5 +125,4 @@
public abstract String toString();
abstract S getThis();
-
}
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/responses/AbstractUnmodifiableResponseImpl.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/responses/AbstractUnmodifiableResponseImpl.java
index ce9f554..28cd4ed 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/responses/AbstractUnmodifiableResponseImpl.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/responses/AbstractUnmodifiableResponseImpl.java
@@ -22,7 +22,7 @@
*
*
* Copyright 2010 Sun Microsystems, Inc.
- * Portions copyright 2012 ForgeRock AS.
+ * Portions copyright 2012-2013 ForgeRock AS.
*/
package org.forgerock.opendj.ldap.responses;
@@ -75,38 +75,38 @@
* {@inheritDoc}
*/
@Override
+ public boolean containsControl(final String oid) {
+ return impl.containsControl(oid);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public final <C extends Control> C getControl(final ControlDecoder<C> decoder,
final DecodeOptions options) throws DecodeException {
Validator.ensureNotNull(decoder, options);
final List<Control> controls = impl.getControls();
-
- // Avoid creating an iterator if possible.
- if (controls.isEmpty()) {
+ final Control control = AbstractResponseImpl.getControl(controls, decoder.getOID());
+ if (control != null) {
+ // Got a match. Return a defensive copy only if necessary.
+ final C decodedControl = decoder.decodeControl(control, options);
+ if (decodedControl != control) {
+ // This was not the original control so return it
+ // immediately.
+ return decodedControl;
+ } else if (decodedControl instanceof GenericControl) {
+ // Generic controls are immutable, so return it immediately.
+ return decodedControl;
+ } else {
+ // Re-decode to get defensive copy.
+ final GenericControl genericControl = GenericControl.newControl(control);
+ return decoder.decodeControl(genericControl, options);
+ }
+ } else {
return null;
}
-
- for (final Control control : controls) {
- if (control.getOID().equals(decoder.getOID())) {
- // Got a match. Return a defensive copy only if necessary.
- final C decodedControl = decoder.decodeControl(control, options);
-
- if (decodedControl != control) {
- // This was not the original control so return it
- // immediately.
- return decodedControl;
- } else if (decodedControl instanceof GenericControl) {
- // Generic controls are immutable, so return it immediately.
- return decodedControl;
- } else {
- // Re-decode to get defensive copy.
- final GenericControl genericControl = GenericControl.newControl(control);
- return decoder.decodeControl(genericControl, options);
- }
- }
- }
-
- return null;
}
/**
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/responses/Response.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/responses/Response.java
index 829ceba..e2632df 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/responses/Response.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/responses/Response.java
@@ -22,7 +22,7 @@
*
*
* Copyright 2009 Sun Microsystems, Inc.
- * Portions copyright 2012 ForgeRock AS.
+ * Portions copyright 2012-2013 ForgeRock AS.
*/
package org.forgerock.opendj.ldap.responses;
@@ -54,6 +54,17 @@
Response addControl(Control control);
/**
+ * Returns {@code true} if this response contains the specified response
+ * control.
+ *
+ * @param oid
+ * The numeric OID of the response control.
+ * @return {@code true} if this response contains the specified response
+ * control.
+ */
+ boolean containsControl(String oid);
+
+ /**
* Decodes and returns the first control in this response having an OID
* corresponding to the provided control decoder.
*
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/resources/org/forgerock/opendj/ldap/core.properties b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/resources/org/forgerock/opendj/ldap/core.properties
index a051a85..abe82c2 100755
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/resources/org/forgerock/opendj/ldap/core.properties
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/resources/org/forgerock/opendj/ldap/core.properties
@@ -22,7 +22,7 @@
#
#
# Copyright 2010 Sun Microsystems, Inc.
-# Portions copyright 2011-2012 ForgeRock AS
+# Portions copyright 2011-2013 ForgeRock AS
#
ERR_ATTR_SYNTAX_UNKNOWN_APPROXIMATE_MATCHING_RULE=Unable to retrieve \
approximate matching rule %s used as the default for the %s attribute syntax. \
@@ -1405,3 +1405,16 @@
ERR_LDIF_MALFORMED_CONTROL=Unable to parse LDIF change record starting at line %d \
with distinguished name "%s" because it contained a malformed control \
"%s"
+ERR_ENTRY_UNKNOWN_MODIFICATION_TYPE=Unsupported modification type '%s'
+ERR_ENTRY_DUPLICATE_VALUES=Unable to add one or more values to attribute \
+ '%s' because at least one of the values already exists
+ERR_ENTRY_NO_SUCH_VALUE=Unable to remove one or more values from attribute \
+ '%s' because at least one of the attributes does not exist in the entry
+ERR_ENTRY_INCREMENT_NO_SUCH_ATTRIBUTE=Unable to increment the value of attribute \
+ '%s' because that attribute does not exist in the entry
+ERR_ENTRY_INCREMENT_INVALID_VALUE_COUNT=Unable to increment the value of \
+ attribute '%s' because the provided modification did not have exactly \
+ one value to use as the increment
+ERR_ENTRY_INCREMENT_CANNOT_PARSE_AS_INT=Unable to increment the value of \
+ attribute '%s' because either the current value or the increment could \
+ not be parsed as an integer
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java
index c2f9743..dd851f4 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/LDAPServer.java
@@ -22,7 +22,7 @@
*
*
* Copyright 2010 Sun Microsystems, Inc.
- * Portions copyright 2011-2012 ForgeRock AS
+ * Portions copyright 2011-2013 ForgeRock AS
*/
package org.forgerock.opendj.ldap;
@@ -103,6 +103,10 @@
return request.addControl(cntrl);
}
+ public boolean containsControl(final String oid) {
+ return request.containsControl(oid);
+ }
+
public <C extends Control> C getControl(final ControlDecoder<C> decoder,
final DecodeOptions options) throws DecodeException {
return request.getControl(decoder, options);
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/MemoryBackendTestCase.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/MemoryBackendTestCase.java
new file mode 100644
index 0000000..e008a3f
--- /dev/null
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/MemoryBackendTestCase.java
@@ -0,0 +1,455 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2013 ForgeRock AS.
+ */
+package org.forgerock.opendj.ldap;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.forgerock.opendj.ldap.Connections.newInternalConnectionFactory;
+import static org.forgerock.opendj.ldap.Connections.newServerConnectionFactory;
+import static org.forgerock.opendj.ldap.requests.Requests.newAddRequest;
+import static org.forgerock.opendj.ldap.requests.Requests.newDeleteRequest;
+import static org.forgerock.opendj.ldap.requests.Requests.newModifyRequest;
+import static org.forgerock.opendj.ldap.requests.Requests.newSimpleBindRequest;
+import static org.forgerock.opendj.ldif.LDIFEntryReader.valueOfLDIFEntry;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.forgerock.opendj.ldap.controls.AssertionRequestControl;
+import org.forgerock.opendj.ldap.controls.PermissiveModifyRequestControl;
+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.ldif.ConnectionEntryReader;
+import org.forgerock.opendj.ldif.LDIFEntryReader;
+import org.testng.annotations.Test;
+
+/**
+ * Memory backend tests.
+ */
+@SuppressWarnings("javadoc")
+public class MemoryBackendTestCase extends SdkTestCase {
+
+ @Test
+ public void testAdd() throws Exception {
+ final Connection connection = getConnection();
+ final Entry newDomain =
+ valueOfLDIFEntry("dn: dc=new domain,dc=com", "objectClass: domain",
+ "objectClass: top", "dc: new domain");
+ connection.add(newDomain);
+ assertThat(connection.readEntry("dc=new domain,dc=com")).isEqualTo(newDomain);
+ }
+
+ @Test(expectedExceptions = ConstraintViolationException.class)
+ public void testAddAlreadyExists() throws Exception {
+ final Connection connection = getConnection();
+ connection.add(valueOfLDIFEntry("dn: dc=example,dc=com", "objectClass: domain",
+ "objectClass: top", "dc: example"));
+ }
+
+ @Test(expectedExceptions = EntryNotFoundException.class)
+ public void testAddNoParent() throws Exception {
+ final Connection connection = getConnection();
+ connection.add(valueOfLDIFEntry("dn: dc=new domain,dc=missing,dc=com",
+ "objectClass: domain", "objectClass: top", "dc: new domain"));
+ }
+
+ @Test
+ public void testAddPostRead() throws Exception {
+ final Connection connection = getConnection();
+ final Entry newDomain =
+ valueOfLDIFEntry("dn: dc=new domain,dc=com", "objectClass: domain",
+ "objectClass: top", "dc: new domain");
+ assertThat(
+ connection.add(
+ newAddRequest(newDomain)
+ .addControl(PostReadRequestControl.newControl(true))).getControl(
+ PostReadResponseControl.DECODER, new DecodeOptions()).getEntry())
+ .isEqualTo(newDomain);
+ }
+
+ @Test(expectedExceptions = ErrorResultException.class)
+ public void testAddPreRead() throws Exception {
+ final Connection connection = getConnection();
+ final Entry newDomain =
+ valueOfLDIFEntry("dn: dc=new domain,dc=com", "objectClass: domain",
+ "objectClass: top", "dc: new domain");
+ connection.add(newAddRequest(newDomain).addControl(PreReadRequestControl.newControl(true)));
+ }
+
+ @Test
+ public void testCompareFalse() throws Exception {
+ final Connection connection = getConnection();
+ assertThat(connection.compare("dc=example,dc=com", "objectclass", "person").matched())
+ .isFalse();
+ }
+
+ @Test(expectedExceptions = EntryNotFoundException.class)
+ public void testCompareNoSuchObject() throws Exception {
+ final Connection connection = getConnection();
+ connection.compare("uid=missing,ou=people,dc=example,dc=com", "uid", "missing");
+ }
+
+ @Test
+ public void testCompareTrue() throws Exception {
+ final Connection connection = getConnection();
+ assertThat(connection.compare("dc=example,dc=com", "objectclass", "domain").matched())
+ .isTrue();
+ }
+
+ @Test(expectedExceptions = AssertionFailureException.class)
+ public void testDeleteAssertionFalse() throws Exception {
+ final Connection connection = getConnection();
+ connection.delete(newDeleteRequest("dc=xxx,dc=com").addControl(
+ AssertionRequestControl.newControl(true, Filter.valueOf("(objectclass=person)"))));
+ }
+
+ @Test
+ public void testDeleteAssertionTrue() throws Exception {
+ final Connection connection = getConnection();
+ connection.delete(newDeleteRequest("dc=xxx,dc=com").addControl(
+ AssertionRequestControl.newControl(true, Filter.valueOf("(objectclass=domain)"))));
+ }
+
+ @Test(expectedExceptions = EntryNotFoundException.class)
+ public void testDeleteNoSuchObject() throws Exception {
+ final Connection connection = getConnection();
+ connection.delete("uid=missing,ou=people,dc=example,dc=com");
+ }
+
+ @Test
+ public void testDeleteOnLeaf() throws Exception {
+ final Connection connection = getConnection();
+ connection.delete("uid=test1,ou=people,dc=example,dc=com");
+ try {
+ connection.readEntry("dc=example,dc=com");
+ } catch (final EntryNotFoundException expected) {
+ // Do nothing.
+ }
+ }
+
+ @Test(expectedExceptions = ConstraintViolationException.class)
+ public void testDeleteOnNonLeaf() throws Exception {
+ final Connection connection = getConnection();
+ try {
+ connection.delete("dc=example,dc=com");
+ } finally {
+ assertThat(connection.readEntry("dc=example,dc=com")).isNotNull();
+ }
+ }
+
+ @Test(expectedExceptions = ErrorResultException.class)
+ public void testDeletePostRead() throws Exception {
+ final Connection connection = getConnection();
+ connection.delete(newDeleteRequest("dc=xxx,dc=com").addControl(
+ PostReadRequestControl.newControl(true)));
+ }
+
+ @Test
+ public void testDeletePreRead() throws Exception {
+ final Connection connection = getConnection();
+ assertThat(
+ connection.delete(
+ newDeleteRequest("dc=xxx,dc=com").addControl(
+ PreReadRequestControl.newControl(true))).getControl(
+ PreReadResponseControl.DECODER, new DecodeOptions()).getEntry()).isEqualTo(
+ valueOfLDIFEntry("dn: dc=xxx,dc=com", "objectClass: domain", "objectClass: top",
+ "dc: xxx"));
+ }
+
+ @Test
+ public void testDeleteSubtree() throws Exception {
+ final Connection connection = getConnection();
+ connection.deleteSubtree("dc=example,dc=com");
+ for (final String name : Arrays.asList("dc=example,dc=com", "ou=people,dc=example,dc=com",
+ "uid=test1,ou=people,dc=example,dc=com", "uid=test2,ou=people,dc=example,dc=com")) {
+ try {
+ connection.readEntry(name);
+ } catch (final EntryNotFoundException expected) {
+ // Do nothing.
+ }
+ }
+ assertThat(connection.readEntry("dc=xxx,dc=com")).isNotNull();
+ }
+
+ @Test
+ public void testModify() throws Exception {
+ final Connection connection = getConnection();
+ connection.modify("dn: dc=example,dc=com", "changetype: modify", "add: description",
+ "description: test description");
+ assertThat(connection.readEntry("dc=example,dc=com")).isEqualTo(
+ valueOfLDIFEntry("dn: dc=example,dc=com", "objectClass: domain",
+ "objectClass: top", "dc: example", "description: test description"));
+ }
+
+ @Test(expectedExceptions = AssertionFailureException.class)
+ public void testModifyAssertionFalse() throws Exception {
+ final Connection connection = getConnection();
+ connection.modify(newModifyRequest("dn: dc=example,dc=com", "changetype: modify",
+ "add: description", "description: test description").addControl(
+ AssertionRequestControl.newControl(true, Filter.valueOf("(objectclass=person)"))));
+ }
+
+ @Test
+ public void testModifyAssertionTrue() throws Exception {
+ final Connection connection = getConnection();
+ connection.modify(newModifyRequest("dn: dc=example,dc=com", "changetype: modify",
+ "add: description", "description: test description").addControl(
+ AssertionRequestControl.newControl(true, Filter.valueOf("(objectclass=domain)"))));
+ }
+
+ @Test
+ public void testModifyBindPostRead() throws Exception {
+ final Connection connection = getConnection();
+ assertThat(
+ connection.modify(
+ newModifyRequest("dn: dc=example,dc=com", "changetype: modify",
+ "add: description", "description: test description").addControl(
+ PostReadRequestControl.newControl(true))).getControl(
+ PostReadResponseControl.DECODER, new DecodeOptions()).getEntry())
+ .isEqualTo(
+ valueOfLDIFEntry("dn: dc=example,dc=com", "objectClass: domain",
+ "objectClass: top", "dc: example", "description: test description"));
+ }
+
+ @Test
+ public void testModifyIncrement() throws Exception {
+ final Connection connection = getConnection();
+ connection.modify("dn: dc=example,dc=com", "changetype: modify", "add: integer",
+ "integer: 100", "-", "increment: integer", "integer: 10");
+ assertThat(connection.readEntry("dc=example,dc=com")).isEqualTo(
+ valueOfLDIFEntry("dn: dc=example,dc=com", "objectClass: domain",
+ "objectClass: top", "dc: example", "integer: 110"));
+ }
+
+ @Test(expectedExceptions = ConstraintViolationException.class)
+ public void testModifyIncrementBadDelta() throws Exception {
+ final Connection connection = getConnection();
+ connection.modify("dn: dc=example,dc=com", "changetype: modify", "add: integer",
+ "integer: 100", "-", "increment: integer", "integer: nan");
+ }
+
+ @Test(expectedExceptions = ConstraintViolationException.class)
+ public void testModifyIncrementBadValue() throws Exception {
+ final Connection connection = getConnection();
+ connection.modify("dn: dc=example,dc=com", "changetype: modify", "add: integer",
+ "integer: nan", "-", "increment: integer", "integer: 10");
+ }
+
+ @Test(expectedExceptions = EntryNotFoundException.class)
+ public void testModifyNoSuchObject() throws Exception {
+ final Connection connection = getConnection();
+ connection.modify("dn: dc=missing,dc=com", "changetype: modify", "add: description",
+ "description: test description");
+ }
+
+ @Test
+ public void testModifyPermissiveWithDuplicateValues() throws Exception {
+ final Connection connection = getConnection();
+ connection.modify(newModifyRequest("dn: dc=example,dc=com", "changetype: modify",
+ "add: dc", "dc: example").addControl(
+ PermissiveModifyRequestControl.newControl(true)));
+ assertThat(connection.readEntry("dc=example,dc=com")).isEqualTo(
+ valueOfLDIFEntry("dn: dc=example,dc=com", "objectClass: domain",
+ "objectClass: top", "dc: example"));
+ }
+
+ @Test
+ public void testModifyPermissiveWithMissingValues() throws Exception {
+ final Connection connection = getConnection();
+ connection.modify(newModifyRequest("dn: dc=example,dc=com", "changetype: modify",
+ "delete: dc", "dc: xxx")
+ .addControl(PermissiveModifyRequestControl.newControl(true)));
+ assertThat(connection.readEntry("dc=example,dc=com")).isEqualTo(
+ valueOfLDIFEntry("dn: dc=example,dc=com", "objectClass: domain",
+ "objectClass: top", "dc: example"));
+ }
+
+ @Test
+ public void testModifyPreRead() throws Exception {
+ final Connection connection = getConnection();
+ assertThat(
+ connection.modify(
+ newModifyRequest("dn: dc=example,dc=com", "changetype: modify",
+ "add: description", "description: test description").addControl(
+ PreReadRequestControl.newControl(true))).getControl(
+ PreReadResponseControl.DECODER, new DecodeOptions()).getEntry()).isEqualTo(
+ valueOfLDIFEntry("dn: dc=example,dc=com", "objectClass: domain",
+ "objectClass: top", "dc: example"));
+ }
+
+ @Test(expectedExceptions = ConstraintViolationException.class)
+ public void testModifyStrictWithDuplicateValues() throws Exception {
+ final Connection connection = getConnection();
+ connection.modify("dn: dc=example,dc=com", "changetype: modify", "add: dc", "dc: example");
+ }
+
+ @Test(expectedExceptions = ConstraintViolationException.class)
+ public void testModifyStrictWithMissingValues() throws Exception {
+ final Connection connection = getConnection();
+ connection.modify("dn: dc=example,dc=com", "changetype: modify", "delete: dc", "dc: xxx");
+ }
+
+ @Test
+ public void testSearchBase() throws Exception {
+ final Connection connection = getConnection();
+ assertThat(connection.readEntry("dc=example,dc=com")).isEqualTo(
+ valueOfLDIFEntry("dn: dc=example,dc=com", "objectClass: domain",
+ "objectClass: top", "dc: example"));
+ }
+
+ @Test(expectedExceptions = EntryNotFoundException.class)
+ public void testSearchBaseNoSuchObject() throws Exception {
+ final Connection connection = getConnection();
+ connection.readEntry("dc=missing,dc=com");
+ }
+
+ @Test
+ public void testSearchOneLevel() throws Exception {
+ final Connection connection = getConnection();
+ final ConnectionEntryReader reader =
+ connection.search("dc=com", SearchScope.SINGLE_LEVEL, "(objectClass=*)");
+ assertThat(reader.readEntry()).isEqualTo(
+ valueOfLDIFEntry("dn: dc=example,dc=com", "objectClass: domain",
+ "objectClass: top", "dc: example"));
+ assertThat(reader.readEntry()).isEqualTo(
+ valueOfLDIFEntry("dn: dc=xxx,dc=com", "objectClass: domain", "objectClass: top",
+ "dc: xxx"));
+ assertThat(reader.hasNext()).isFalse();
+ }
+
+ @Test
+ public void testSearchSubtree() throws Exception {
+ final Connection connection = getConnection();
+ assertThat(
+ connection.searchSingleEntry("dc=example,dc=com", SearchScope.WHOLE_SUBTREE,
+ "(uid=test1)")).isEqualTo(getUser1Entry());
+ }
+
+ @Test(expectedExceptions = EntryNotFoundException.class)
+ public void testSearchSubtreeNotFound() throws Exception {
+ final Connection connection = getConnection();
+ connection.searchSingleEntry("dc=example,dc=com", SearchScope.WHOLE_SUBTREE,
+ "(uid=missing)");
+ }
+
+ @Test
+ public void testSimpleBind() throws Exception {
+ final Connection connection = getConnection();
+ connection.bind("uid=test1,ou=people,dc=example,dc=com", "password".toCharArray());
+ }
+
+ @Test(expectedExceptions = AuthenticationException.class)
+ public void testSimpleBindBadPassword() throws Exception {
+ final Connection connection = getConnection();
+ connection.bind("uid=test1,ou=people,dc=example,dc=com", "bad".toCharArray());
+ }
+
+ @Test(expectedExceptions = AuthenticationException.class)
+ public void testSimpleBindNoSuchUser() throws Exception {
+ final Connection connection = getConnection();
+ connection.bind("uid=missing,ou=people,dc=example,dc=com", "password".toCharArray());
+ }
+
+ @Test
+ public void testSimpleBindPostRead() throws Exception {
+ final Connection connection = getConnection();
+ assertThat(
+ connection.bind(
+ newSimpleBindRequest("uid=test1,ou=people,dc=example,dc=com",
+ "password".toCharArray()).addControl(
+ PostReadRequestControl.newControl(true))).getControl(
+ PostReadResponseControl.DECODER, new DecodeOptions()).getEntry())
+ .isEqualTo(getUser1Entry());
+ }
+
+ @Test
+ public void testSimpleBindPreRead() throws Exception {
+ final Connection connection = getConnection();
+ assertThat(
+ connection.bind(
+ newSimpleBindRequest("uid=test1,ou=people,dc=example,dc=com",
+ "password".toCharArray()).addControl(
+ PreReadRequestControl.newControl(true))).getControl(
+ PreReadResponseControl.DECODER, new DecodeOptions()).getEntry()).isEqualTo(
+ getUser1Entry());
+ }
+
+ private Connection getConnection() throws IOException, ErrorResultException {
+ // @formatter:off
+ final MemoryBackend backend =
+ new MemoryBackend(new LDIFEntryReader(
+ "dn: dc=com",
+ "objectClass: domain",
+ "objectClass: top",
+ "dc: com",
+ "",
+ "dn: dc=example,dc=com",
+ "objectClass: domain",
+ "objectClass: top",
+ "dc: example",
+ "",
+ "dn: ou=People,dc=example,dc=com",
+ "objectClass: organizationalunit",
+ "objectClass: top",
+ "ou: People",
+ "",
+ "dn: uid=test1,ou=People,dc=example,dc=com",
+ "objectClass: top",
+ "objectClass: person",
+ "uid: test1",
+ "userpassword: password",
+ "cn: test user 1",
+ "sn: user 1",
+ "",
+ "dn: uid=test2,ou=People,dc=example,dc=com",
+ "objectClass: top",
+ "objectClass: person",
+ "uid: test2",
+ "userpassword: password",
+ "cn: test user 2",
+ "sn: user 2",
+ "",
+ "dn: dc=xxx,dc=com",
+ "objectClass: domain",
+ "objectClass: top",
+ "dc: xxx"
+ ));
+ // @formatter:on
+
+ final Connection connection =
+ newInternalConnectionFactory(newServerConnectionFactory(backend), null)
+ .getConnection();
+ return connection;
+ }
+
+ private Entry getUser1Entry() {
+ return valueOfLDIFEntry("dn: uid=test1,ou=People,dc=example,dc=com", "objectClass: top",
+ "objectClass: person", "uid: test1", "userpassword: password", "cn: test user 1",
+ "sn: user 1");
+ }
+
+}
--
Gitblit v1.10.0