/* * The contents of this file are subject to the terms of the Common Development and * Distribution License (the License). You may not use this file except in compliance with the * License. * * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the * specific language governing permission and limitations under the License. * * When distributing Covered Software, include this CDDL Header Notice in each file and include * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL * Header, with the fields enclosed by brackets [] replaced by your own identifying * information: "Portions copyright [year] [name of copyright owner]". * * Copyright 2016 ForgeRock AS. */ package org.forgerock.opendj.rest2ldap; import static org.forgerock.http.routing.RoutingMode.EQUALS; import static org.forgerock.http.routing.RoutingMode.STARTS_WITH; import static org.forgerock.json.resource.RouteMatchers.requestUriMatcher; import static org.forgerock.opendj.ldap.Filter.objectClassPresent; import static org.forgerock.opendj.ldap.SearchScope.BASE_OBJECT; import static org.forgerock.opendj.ldap.SearchScope.SINGLE_LEVEL; import static org.forgerock.opendj.ldap.requests.Requests.newSearchRequest; import static org.forgerock.opendj.rest2ldap.Rest2Ldap.asResourceException; import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*; import static org.forgerock.opendj.rest2ldap.RoutingContext.newRoutingContext; import static org.forgerock.opendj.rest2ldap.Utils.newBadRequestException; import static org.forgerock.util.promise.Promises.newResultPromise; import org.forgerock.api.models.ApiDescription; import org.forgerock.http.ApiProducer; import org.forgerock.http.routing.UriRouterContext; import org.forgerock.i18n.LocalizedIllegalArgumentException; import org.forgerock.json.resource.ActionRequest; import org.forgerock.json.resource.ActionResponse; import org.forgerock.json.resource.BadRequestException; import org.forgerock.json.resource.CreateRequest; import org.forgerock.json.resource.DeleteRequest; import org.forgerock.json.resource.NotSupportedException; import org.forgerock.json.resource.PatchRequest; import org.forgerock.json.resource.QueryRequest; import org.forgerock.json.resource.QueryResourceHandler; import org.forgerock.json.resource.QueryResponse; import org.forgerock.json.resource.ReadRequest; import org.forgerock.json.resource.Request; import org.forgerock.json.resource.ResourceException; import org.forgerock.json.resource.ResourceResponse; import org.forgerock.json.resource.Router; import org.forgerock.json.resource.UpdateRequest; import org.forgerock.opendj.ldap.Attribute; import org.forgerock.opendj.ldap.AttributeDescription; import org.forgerock.opendj.ldap.ByteString; import org.forgerock.opendj.ldap.Connection; import org.forgerock.opendj.ldap.DN; import org.forgerock.opendj.ldap.Entry; import org.forgerock.opendj.ldap.Filter; import org.forgerock.opendj.ldap.LdapException; import org.forgerock.opendj.ldap.LinkedAttribute; import org.forgerock.opendj.ldap.RDN; import org.forgerock.opendj.ldap.requests.SearchRequest; import org.forgerock.opendj.ldap.responses.SearchResultEntry; import org.forgerock.services.context.Context; import org.forgerock.util.AsyncFunction; import org.forgerock.util.Function; import org.forgerock.util.promise.Promise; /** * Defines a one-to-many relationship between a parent resource and its children. Removal of the parent resource * implies that the children (the sub-resources) are also removed. Collections support all request types. */ public final class SubResourceCollection extends SubResource { /** The LDAP object classes associated with the glue entries forming the DN template. */ private final Attribute glueObjectClasses = new LinkedAttribute("objectClass"); private NamingStrategy namingStrategy; private boolean flattenSubtree; SubResourceCollection(final String resourceId) { super(resourceId); useClientDnNaming("uid"); } /** * Gets whether or not this sub-resource should flatten sub-entries in results. * * @return {@code true} if entries deep in the sub-tree are included in a flattened * collection view; {@code false} if only entries at the top level of the DN for this * sub-resource should be returned. */ public boolean shouldFlattenSubtree() { return flattenSubtree; } /** * Indicates that the JSON resource ID must be provided by the user, and will be used for naming the associated LDAP * entry. More specifically, LDAP entry names will be derived by appending a single RDN to the collection's base DN * composed of the specified attribute type and LDAP value taken from the LDAP entry once attribute mapping has been * performed. *
* Note that this naming policy requires that the user provides the resource name when creating new resources, which * means it must be included in the resource content when not specified explicitly in the create request. * * @param dnAttribute * The LDAP attribute which will be used for naming. * @return A reference to this object. */ public SubResourceCollection useClientDnNaming(final String dnAttribute) { this.namingStrategy = new DnNamingStrategy(dnAttribute); return this; } /** * Indicates that the JSON resource ID must be provided by the user, but will not be used for naming the * associated LDAP entry. Instead the JSON resource ID will be taken from the {@code idAttribute} in the LDAP * entry, and the LDAP entry name will be derived by appending a single RDN to the collection's base DN composed * of the {@code dnAttribute} taken from the LDAP entry once attribute mapping has been performed. *
* Note that this naming policy requires that the user provides the resource name when creating new resources, which * means it must be included in the resource content when not specified explicitly in the create request. * * @param dnAttribute * The attribute which will be used for naming LDAP entries. * @param idAttribute * The attribute which will be used for JSON resource IDs. * @return A reference to this object. */ public SubResourceCollection useClientNaming(final String dnAttribute, final String idAttribute) { this.namingStrategy = new AttributeNamingStrategy(dnAttribute, idAttribute, false); return this; } /** * Indicates that the JSON resource ID will be derived from the server provided "entryUUID" LDAP attribute. The * LDAP entry name will be derived by appending a single RDN to the collection's base DN composed of the {@code * dnAttribute} taken from the LDAP entry once attribute mapping has been performed. *
* Note that this naming policy requires that the server provides the resource name when creating new resources, * which means it must not be specified in the create request, nor included in the resource content. * * @param dnAttribute * The attribute which will be used for naming LDAP entries. * @return A reference to this object. */ public SubResourceCollection useServerEntryUuidNaming(final String dnAttribute) { return useServerNaming(dnAttribute, "entryUUID"); } /** * Indicates that the JSON resource ID must not be provided by the user, and will not be used for naming the * associated LDAP entry. Instead the JSON resource ID will be taken from the {@code idAttribute} in the LDAP * entry, and the LDAP entry name will be derived by appending a single RDN to the collection's base DN composed * of the {@code dnAttribute} taken from the LDAP entry once attribute mapping has been performed. *
* Note that this naming policy requires that the server provides the resource name when creating new resources,
* which means it must not be specified in the create request, nor included in the resource content.
*
* @param dnAttribute
* The attribute which will be used for naming LDAP entries.
* @param idAttribute
* The attribute which will be used for JSON resource IDs.
* @return A reference to this object.
*/
public SubResourceCollection useServerNaming(final String dnAttribute, final String idAttribute) {
this.namingStrategy = new AttributeNamingStrategy(dnAttribute, idAttribute, true);
return this;
}
/**
* Sets the relative URL template beneath which the sub-resources will be located. The template may be empty
* indicating that the sub-resources will be located directly beneath the parent resource. Any URL template
* variables will be substituted into the {@link #dnTemplate(String) DN template}.
*
* @param urlTemplate
* The relative URL template.
* @return A reference to this object.
*/
public SubResourceCollection urlTemplate(final String urlTemplate) {
this.urlTemplate = urlTemplate;
return this;
}
/**
* Sets the relative DN template beneath which the sub-resource LDAP entries will be located. The template may be
* empty indicating that the LDAP entries will be located directly beneath the parent LDAP entry. Any DN template
* variables will be substituted using values extracted from the {@link #urlTemplate(String) URL template}.
*
* @param dnTemplate
* The relative DN template.
* @return A reference to this object.
*/
public SubResourceCollection dnTemplate(final String dnTemplate) {
this.dnTemplateString = dnTemplate;
return this;
}
/**
* Specifies an LDAP object class which is to be associated with any intermediate "glue" entries forming the DN
* template. Multiple object classes may be specified.
*
* @param objectClass
* An LDAP object class which is to be associated with any intermediate "glue" entries forming the DN
* template.
* @return A reference to this object.
*/
public SubResourceCollection glueObjectClass(final String objectClass) {
this.glueObjectClasses.add(objectClass);
return this;
}
/**
* Specifies one or more LDAP object classes which is to be associated with any intermediate "glue" entries
* forming the DN template. Multiple object classes may be specified.
*
* @param objectClasses
* The LDAP object classes which is to be associated with any intermediate "glue" entries forming the DN
* template.
* @return A reference to this object.
*/
public SubResourceCollection glueObjectClasses(final String... objectClasses) {
this.glueObjectClasses.add((Object[]) objectClasses);
return this;
}
/**
* Indicates whether this sub-resource collection only supports read and query operations.
*
* @param isReadOnly
* {@code true} if this sub-resource collection is read-only.
* @return A reference to this object.
*/
public SubResourceCollection isReadOnly(final boolean isReadOnly) {
this.isReadOnly = isReadOnly;
return this;
}
/**
* Controls whether or not LDAP entries in the hierarchy below the root entry of the resource
* collection are included in the list of resources (essentially, flattening the hierarchy
* into one collection of resources).
*
* This can only be used if the resource is read-only. The default is not to flatten, which
* preserves the legacy behavior of Rest2LDAP.
*
* @param flattenSubtree
* Whether or not to flatten the hierarchy by searching the entire subtree.
* @return A reference to this object.
* @throws IllegalArgumentException
* If the configuration is invalid.
*/
public SubResourceCollection flattenSubtree(boolean flattenSubtree) {
if (flattenSubtree && !this.isReadOnly) {
throw new LocalizedIllegalArgumentException(
ERR_CONFIG_MUST_BE_READ_ONLY_TO_FLATTEN_SUBTREE.get());
}
this.flattenSubtree = flattenSubtree;
return this;
}
@Override
Router addRoutes(final Router router) {
router.addRoute(requestUriMatcher(EQUALS, urlTemplate), readOnly(new CollectionHandler()));
router.addRoute(requestUriMatcher(EQUALS, urlTemplate + "/{id}"), readOnly(new InstanceHandler()));
router.addRoute(requestUriMatcher(STARTS_WITH, urlTemplate + "/{id}"), readOnly(new SubResourceHandler()));
return router;
}
Promise
* This avoids problems when trying to {@link ApiProducer#merge(java.util.List) merge}
* {@link ApiDescription}s with the same path.
*/
@Override
public ApiDescription api(ApiProducer