Fix OPENDJ-354: Implement a RequestHandler which provides an in-memory backend
* added class MemoryBackend
* added Entries.modifyEntry() and included support for increment
* added Connection.deleteSubtree()
* added Request.containsControl() and Response.containsControl()
* aligned attributes APIs in Post/Pre read controls with SearchRequest (i.e. use List)
* refactored server example to use new memory backend
2 files added
18 files modified
| | |
| | | * |
| | | * |
| | | * 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; |
| | | |
| | | /** |
| | |
| | | * </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. |
| | |
| | | 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 = |
| | |
| | | .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); |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 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. |
| | | } |
| | |
| | | * 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); |
| | |
| | | * |
| | | * |
| | | * Copyright 2009-2010 Sun Microsystems, Inc. |
| | | * Portions copyright 2011-2012 ForgeRock AS |
| | | * Portions copyright 2011-2013 ForgeRock AS |
| | | */ |
| | | |
| | | package org.forgerock.opendj.ldap; |
| | |
| | | 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; |
| | |
| | | * {@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); |
| | |
| | | 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. |
| | | * |
| | |
| | | * |
| | | * |
| | | * 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; |
| | |
| | | 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; |
| | |
| | | /** |
| | | * {@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); |
| | | } |
| | | |
| | |
| | | |
| | | } |
| | | |
| | | 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>() { |
| | | |
| | |
| | | |
| | | }; |
| | | |
| | | 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). |
| | |
| | | } |
| | | |
| | | /** |
| | | * 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 |
| | |
| | | } |
| | | } |
| | | |
| | | 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. |
| | |
| | | || 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 |
| | |
| | | } |
| | | |
| | | @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); |
| New file |
| | |
| | | /* |
| | | * 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()))); |
| | | } |
| | | } |
| | |
| | | */ |
| | | public final class Modification { |
| | | private final ModificationType modificationType; |
| | | |
| | | private final Attribute attribute; |
| | | |
| | | /** |
| | |
| | | * fully immutable: |
| | | * |
| | | * <pre> |
| | | * Modification change = new Modification(modificationType, Types.unmodifiableAttribute(attribute)); |
| | | * Modification change = new Modification(modificationType, Attributes |
| | | * .unmodifiableAttribute(attribute)); |
| | | * </pre> |
| | | * |
| | | * @param modificationType |
| | |
| | | */ |
| | | public Modification(final ModificationType modificationType, final Attribute attribute) { |
| | | Validator.ensureNotNull(modificationType, attribute); |
| | | |
| | | this.modificationType = modificationType; |
| | | this.attribute = attribute; |
| | | } |
| | |
| | | * |
| | | * |
| | | * 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; |
| | |
| | | */ |
| | | 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. |
| | |
| | | } |
| | | |
| | | 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) { |
| | |
| | | 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))); |
| | | } |
| | | } |
| | | |
| | |
| | | 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; |
| | | } |
| | | |
| | |
| | | * |
| | | * |
| | | * 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; |
| | |
| | | */ |
| | | 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. |
| | |
| | | } |
| | | |
| | | 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) { |
| | |
| | | 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))); |
| | | } |
| | | } |
| | | |
| | |
| | | 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; |
| | | } |
| | | |
| | |
| | | * |
| | | * |
| | | * Copyright 2010 Sun Microsystems, Inc. |
| | | * Portions copyright 2012 ForgeRock AS. |
| | | * Portions copyright 2012-2013 ForgeRock AS. |
| | | */ |
| | | |
| | | package org.forgerock.opendj.ldap.requests; |
| | |
| | | * 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>(); |
| | | |
| | | /** |
| | |
| | | * @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)); |
| | | } |
| | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public final R addControl(final Control control) { |
| | | Validator.ensureNotNull(control); |
| | | controls.add(control); |
| | |
| | | /** |
| | | * {@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; |
| | | } |
| | |
| | | * |
| | | * |
| | | * Copyright 2010 Sun Microsystems, Inc. |
| | | * Portions copyright 2012 ForgeRock AS. |
| | | * Portions copyright 2012-2013 ForgeRock AS. |
| | | */ |
| | | |
| | | package org.forgerock.opendj.ldap.requests; |
| | |
| | | * {@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()) { |
| | | return null; |
| | | } |
| | | |
| | | for (final Control control : controls) { |
| | | if (control.getOID().equals(decoder.getOID())) { |
| | | 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. |
| | |
| | | final GenericControl genericControl = GenericControl.newControl(control); |
| | | return decoder.decodeControl(genericControl, options); |
| | | } |
| | | } |
| | | } |
| | | |
| | | } else { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | |
| | | @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 |
| | |
| | | * |
| | | * |
| | | * Copyright 2009-2010 Sun Microsystems, Inc. |
| | | * Portions copyright 2012 ForgeRock AS. |
| | | * Portions copyright 2012-2013 ForgeRock AS. |
| | | */ |
| | | |
| | | package org.forgerock.opendj.ldap.requests; |
| | |
| | | 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. |
| | | * |
| | |
| | | * |
| | | * |
| | | * Copyright 2010 Sun Microsystems, Inc. |
| | | * Portions copyright 2012 ForgeRock AS. |
| | | * Portions copyright 2012-2013 ForgeRock AS. |
| | | */ |
| | | |
| | | package org.forgerock.opendj.ldap.responses; |
| | |
| | | * 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>(); |
| | | |
| | | /** |
| | |
| | | * @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)); |
| | | } |
| | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public final S addControl(final Control control) { |
| | | Validator.ensureNotNull(control); |
| | | controls.add(control); |
| | |
| | | /** |
| | | * {@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; |
| | | } |
| | |
| | | public abstract String toString(); |
| | | |
| | | abstract S getThis(); |
| | | |
| | | } |
| | |
| | | * |
| | | * |
| | | * Copyright 2010 Sun Microsystems, Inc. |
| | | * Portions copyright 2012 ForgeRock AS. |
| | | * Portions copyright 2012-2013 ForgeRock AS. |
| | | */ |
| | | |
| | | package org.forgerock.opendj.ldap.responses; |
| | |
| | | * {@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()) { |
| | | return null; |
| | | } |
| | | |
| | | for (final Control control : controls) { |
| | | if (control.getOID().equals(decoder.getOID())) { |
| | | 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. |
| | |
| | | final GenericControl genericControl = GenericControl.newControl(control); |
| | | return decoder.decodeControl(genericControl, options); |
| | | } |
| | | } |
| | | } |
| | | |
| | | } else { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | |
| | | * |
| | | * |
| | | * Copyright 2009 Sun Microsystems, Inc. |
| | | * Portions copyright 2012 ForgeRock AS. |
| | | * Portions copyright 2012-2013 ForgeRock AS. |
| | | */ |
| | | |
| | | package org.forgerock.opendj.ldap.responses; |
| | |
| | | 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. |
| | | * |
| | |
| | | # |
| | | # |
| | | # 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. \ |
| | |
| | | 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 |
| | |
| | | * |
| | | * |
| | | * Copyright 2010 Sun Microsystems, Inc. |
| | | * Portions copyright 2011-2012 ForgeRock AS |
| | | * Portions copyright 2011-2013 ForgeRock AS |
| | | */ |
| | | |
| | | package org.forgerock.opendj.ldap; |
| | |
| | | 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); |
| New file |
| | |
| | | /* |
| | | * 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"); |
| | | } |
| | | |
| | | } |