From e4c0edea06c8fee28369f03f393b7d54b2b6235c Mon Sep 17 00:00:00 2001
From: Jean-Noël Rouvignac <jean-noel.rouvignac@forgerock.com>
Date: Fri, 16 Sep 2016 13:25:06 +0000
Subject: [PATCH] OPENDJ-3246 Return the CREST descriptor over REST for rest2ldap endpoints
---
opendj-core/src/main/java/com/forgerock/opendj/util/ManifestUtil.java | 87 ++++
opendj-server-legacy/src/main/java/org/opends/server/util/BuildVersion.java | 11
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimplePropertyMapper.java | 38 +
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/JsonConstantPropertyMapper.java | 75 ++
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceCollection.java | 19
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapHttpApplication.java | 27 +
opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/Rest2LdapEndpoint.java | 28 +
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ObjectPropertyMapper.java | 52 ++
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ResourceTypePropertyMapper.java | 17
opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfiguratorTest.java | 109 +++++
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferencePropertyMapper.java | 21
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResource.java | 7
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2Ldap.java | 53 --
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Resource.java | 409 +++++++++++++++++++
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AbstractLdapPropertyMapper.java | 32 +
opendj-server-legacy/src/main/java/org/opends/server/protocols/http/HTTPConnectionHandler.java | 21
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/PropertyMapper.java | 6
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceSingleton.java | 7
opendj-server-legacy/src/main/java/org/opends/server/core/HttpEndpointConfigManager.java | 5
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AbstractRequestHandler.java | 29 +
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DescribableRequestHandler.java | 132 ++++++
opendj-cli/src/main/java/com/forgerock/opendj/cli/ToolVersionHandler.java | 23 -
22 files changed, 1,098 insertions(+), 110 deletions(-)
diff --git a/opendj-cli/src/main/java/com/forgerock/opendj/cli/ToolVersionHandler.java b/opendj-cli/src/main/java/com/forgerock/opendj/cli/ToolVersionHandler.java
index cbb713e..a2d03dd 100644
--- a/opendj-cli/src/main/java/com/forgerock/opendj/cli/ToolVersionHandler.java
+++ b/opendj-cli/src/main/java/com/forgerock/opendj/cli/ToolVersionHandler.java
@@ -15,12 +15,7 @@
*/
package com.forgerock.opendj.cli;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.util.Enumeration;
-import java.util.jar.Attributes;
-import java.util.jar.Manifest;
+import com.forgerock.opendj.util.ManifestUtil;
/** Class that prints the version of the SDK to System.out. */
public final class ToolVersionHandler implements VersionHandler {
@@ -64,20 +59,6 @@
}
private String getVersion() {
- try {
- final Enumeration<URL> manifests = getClass().getClassLoader().getResources("META-INF/MANIFEST.MF");
- while (manifests.hasMoreElements()) {
- final URL manifestUrl = manifests.nextElement();
- if (manifestUrl.toString().contains(moduleName)) {
- try (InputStream manifestStream = manifestUrl.openStream()) {
- final Attributes attrs = new Manifest(manifestStream).getMainAttributes();
- return attrs.getValue("Bundle-Version") + " (revision " + attrs.getValue("SCM-Revision") + ")";
- }
- }
- }
- return null;
- } catch (IOException e) {
- throw new RuntimeException("IOException while determining opendj tool version", e);
- }
+ return ManifestUtil.getVersionWithRevision(moduleName);
}
}
diff --git a/opendj-core/src/main/java/com/forgerock/opendj/util/ManifestUtil.java b/opendj-core/src/main/java/com/forgerock/opendj/util/ManifestUtil.java
new file mode 100644
index 0000000..e11d841
--- /dev/null
+++ b/opendj-core/src/main/java/com/forgerock/opendj/util/ManifestUtil.java
@@ -0,0 +1,87 @@
+/*
+ * 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 com.forgerock.opendj.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+import org.forgerock.util.Pair;
+
+/** Utility methods reading information from {@code opendj-core}'s manifest. */
+public final class ManifestUtil {
+ private static final String OPENDJ_CORE_VERSION;
+ private static final String OPENDJ_CORE_VERSION_WITH_REVISION;
+
+ static {
+ final Pair<String, String> versions = getVersions("opendj-core");
+ OPENDJ_CORE_VERSION = versions.getFirst();
+ OPENDJ_CORE_VERSION_WITH_REVISION = versions.getSecond();
+ }
+
+ /**
+ * Returns the version with the revision contained in the module manifest whose name is provided.
+ *
+ * @param moduleName The module name for which to retrieve the version number
+ * @return the version with the revision contained in the module manifest whose name is provided.
+ */
+ public static String getVersionWithRevision(String moduleName) {
+ if ("opendj-core".equals(moduleName)) {
+ return OPENDJ_CORE_VERSION_WITH_REVISION;
+ }
+ return getVersions(moduleName).getSecond();
+ }
+
+ /**
+ * Returns the bundle version contained in the module manifest whose name is provided.
+ *
+ * @param moduleName The module name for which to retrieve the version number
+ * @return the bundle version contained in the module manifest whose name is provided.
+ */
+ public static String getBundleVersion(String moduleName) {
+ if ("opendj-core".equals(moduleName)) {
+ return OPENDJ_CORE_VERSION;
+ }
+ return getVersions(moduleName).getFirst();
+ }
+
+ private static Pair<String, String> getVersions(String moduleName) {
+ try {
+ final Enumeration<URL> manifests = ManifestUtil.class.getClassLoader().getResources("META-INF/MANIFEST.MF");
+ while (manifests.hasMoreElements()) {
+ final URL manifestUrl = manifests.nextElement();
+ if (manifestUrl.toString().contains(moduleName)) {
+ try (InputStream manifestStream = manifestUrl.openStream()) {
+ final Attributes attrs = new Manifest(manifestStream).getMainAttributes();
+ final String bundleVersion = attrs.getValue("Bundle-Version");
+ return Pair.of(bundleVersion,
+ bundleVersion + " (revision " + attrs.getValue("SCM-Revision") + ")");
+ }
+ }
+ }
+ return null;
+ } catch (IOException e) {
+ throw new RuntimeException("IOException while determining opendj tool version", e);
+ }
+ }
+
+ private ManifestUtil() {
+ // do not instantiate util classes
+ }
+}
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AbstractLdapPropertyMapper.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AbstractLdapPropertyMapper.java
index 0feff58..f529243 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AbstractLdapPropertyMapper.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AbstractLdapPropertyMapper.java
@@ -18,6 +18,8 @@
import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
+
+import static org.forgerock.api.enums.WritePolicy.WRITE_ON_CREATE;
import static org.forgerock.opendj.ldap.Attributes.emptyAttribute;
import static org.forgerock.opendj.rest2ldap.Rest2Ldap.asResourceException;
import static org.forgerock.opendj.rest2ldap.Utils.isNullOrEmpty;
@@ -85,6 +87,16 @@
return getThis();
}
+ @Override
+ boolean isRequired() {
+ return isRequired;
+ }
+
+ @Override
+ boolean isMultiValued() {
+ return isMultiValued;
+ }
+
/**
* Indicates whether the LDAP attribute supports updates. The default is {@link WritabilityPolicy#READ_WRITE}.
*
@@ -351,4 +363,24 @@
}
}
+ void putWritabilityProperties(JsonValue jsonSchema) {
+ switch (writabilityPolicy != null ? writabilityPolicy : WritabilityPolicy.READ_WRITE) {
+ case CREATE_ONLY:
+ jsonSchema.put("writePolicy", WRITE_ON_CREATE.toString());
+ jsonSchema.put("errorOnWritePolicyFailure", true);
+ break;
+ case CREATE_ONLY_DISCARD_WRITES:
+ jsonSchema.put("writePolicy", WRITE_ON_CREATE.toString());
+ break;
+ case READ_ONLY:
+ jsonSchema.put("readOnly", true);
+ jsonSchema.put("errorOnWritePolicyFailure", true);
+ break;
+ case READ_ONLY_DISCARD_WRITES:
+ jsonSchema.put("readOnly", true);
+ break;
+ default:
+ break;
+ }
+ }
}
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AbstractRequestHandler.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AbstractRequestHandler.java
index 4b02050..8a71663 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AbstractRequestHandler.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AbstractRequestHandler.java
@@ -15,6 +15,8 @@
*/
package org.forgerock.opendj.rest2ldap;
+import org.forgerock.api.models.ApiDescription;
+import org.forgerock.http.ApiProducer;
import org.forgerock.json.resource.ActionRequest;
import org.forgerock.json.resource.ActionResponse;
import org.forgerock.json.resource.CreateRequest;
@@ -31,13 +33,14 @@
import org.forgerock.json.resource.ResourceResponse;
import org.forgerock.json.resource.UpdateRequest;
import org.forgerock.services.context.Context;
+import org.forgerock.services.descriptor.Describable;
import org.forgerock.util.promise.Promise;
/**
* An abstract base class from which request handlers may be easily implemented. The default implementation of each
* method is to invoke the {@link #handleRequest(Context, Request)} method.
*/
-public abstract class AbstractRequestHandler implements RequestHandler {
+public abstract class AbstractRequestHandler implements RequestHandler, Describable<ApiDescription, Request> {
/** Creates a new {@code AbstractRequestHandler}. */
protected AbstractRequestHandler() {
// Nothing to do.
@@ -96,4 +99,28 @@
protected <V> Promise<V, ResourceException> handleRequest(final Context context, final Request request) {
return new NotSupportedException().asPromise();
}
+
+ @Override
+ public ApiDescription api(ApiProducer<ApiDescription> producer) {
+ // api descriptions that are null will be ignored
+ return null;
+ }
+
+ @Override
+ public ApiDescription handleApiRequest(Context context, Request request) {
+ // api requests are handled at a higher level by org.forgerock.opendj.rest2ldap.DescribableRequestHandler.
+ // So this code is never reached.
+ throw new UnsupportedOperationException("This should be handled by "
+ + "org.forgerock.opendj.rest2ldap.DescribableRequestHandler.handleApiRequest()");
+ }
+
+ @Override
+ public void addDescriptorListener(Describable.Listener listener) {
+ // nothing to do
+ }
+
+ @Override
+ public void removeDescriptorListener(Describable.Listener listener) {
+ // nothing to do
+ }
}
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DescribableRequestHandler.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DescribableRequestHandler.java
new file mode 100644
index 0000000..8770aaf
--- /dev/null
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DescribableRequestHandler.java
@@ -0,0 +1,132 @@
+/*
+ * 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 org.forgerock.api.models.ApiDescription;
+import org.forgerock.http.ApiProducer;
+import org.forgerock.json.resource.ActionRequest;
+import org.forgerock.json.resource.ActionResponse;
+import org.forgerock.json.resource.CreateRequest;
+import org.forgerock.json.resource.DeleteRequest;
+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.RequestHandler;
+import org.forgerock.json.resource.ResourceException;
+import org.forgerock.json.resource.ResourceResponse;
+import org.forgerock.json.resource.UpdateRequest;
+import org.forgerock.services.context.Context;
+import org.forgerock.services.descriptor.Describable;
+import org.forgerock.util.Reject;
+import org.forgerock.util.promise.Promise;
+
+/** Decorator for a request handler that can return an api descriptor of the underlying handler. */
+public class DescribableRequestHandler implements RequestHandler, Describable<ApiDescription, Request> {
+ private final RequestHandler delegate;
+ private final Describable<ApiDescription, Request> describableDelegate;
+ private ApiDescription api;
+
+ /**
+ * Builds an object decorating the provided handler.
+ *
+ * @param handler
+ * the handler to decorate.
+ */
+ @SuppressWarnings("unchecked")
+ public DescribableRequestHandler(final RequestHandler handler) {
+ this.delegate = Reject.checkNotNull(handler);
+ this.describableDelegate = delegate instanceof Describable
+ ? (Describable<ApiDescription, Request>) delegate
+ : null;
+ }
+
+ @Override
+ public Promise<ActionResponse, ResourceException> handleAction(Context context, ActionRequest request) {
+ return delegate.handleAction(wrap(context), request);
+ }
+
+ @Override
+ public Promise<ResourceResponse, ResourceException> handleCreate(Context context, CreateRequest request) {
+ return delegate.handleCreate(wrap(context), request);
+ }
+
+ @Override
+ public Promise<ResourceResponse, ResourceException> handleDelete(Context context, DeleteRequest request) {
+ return delegate.handleDelete(wrap(context), request);
+ }
+
+ @Override
+ public Promise<ResourceResponse, ResourceException> handlePatch(Context context, PatchRequest request) {
+ return delegate.handlePatch(wrap(context), request);
+ }
+
+ @Override
+ public Promise<QueryResponse, ResourceException> handleQuery(
+ Context context, QueryRequest request, QueryResourceHandler handler) {
+ return delegate.handleQuery(wrap(context), request, handler);
+ }
+
+ @Override
+ public Promise<ResourceResponse, ResourceException> handleRead(Context context, ReadRequest request) {
+ return delegate.handleRead(wrap(context), request);
+ }
+
+ @Override
+ public Promise<ResourceResponse, ResourceException> handleUpdate(Context context, UpdateRequest request) {
+ return delegate.handleUpdate(wrap(context), request);
+ }
+
+ /**
+ * Allows sub classes to wrap the provided context and return the wrapping context.
+ *
+ * @param context
+ * the context to wrap
+ * @return the wrapping context that should be used
+ */
+ protected Context wrap(final Context context) {
+ return context;
+ }
+
+ @Override
+ public ApiDescription api(ApiProducer<ApiDescription> producer) {
+ if (describableDelegate != null) {
+ api = describableDelegate.api(producer);
+ }
+ return api;
+ }
+
+ @Override
+ public ApiDescription handleApiRequest(Context context, Request request) {
+ return api;
+ }
+
+ @Override
+ public void addDescriptorListener(Describable.Listener listener) {
+ if (describableDelegate != null) {
+ describableDelegate.addDescriptorListener(listener);
+ }
+ }
+
+ @Override
+ public void removeDescriptorListener(Describable.Listener listener) {
+ if (describableDelegate != null) {
+ describableDelegate.removeDescriptorListener(listener);
+ }
+ }
+}
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/JsonConstantPropertyMapper.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/JsonConstantPropertyMapper.java
index 01525ff..dc6400b 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/JsonConstantPropertyMapper.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/JsonConstantPropertyMapper.java
@@ -16,6 +16,7 @@
package org.forgerock.opendj.rest2ldap;
import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*;
+import static org.forgerock.json.JsonValue.*;
import static org.forgerock.opendj.ldap.Filter.alwaysFalse;
import static org.forgerock.opendj.ldap.Filter.alwaysTrue;
import static org.forgerock.opendj.rest2ldap.Utils.isNullOrEmpty;
@@ -50,6 +51,16 @@
}
@Override
+ boolean isRequired() {
+ return false;
+ }
+
+ @Override
+ boolean isMultiValued() {
+ return false;
+ }
+
+ @Override
public String toString() {
return "constant(" + value + ")";
}
@@ -75,39 +86,38 @@
final JsonPointer path, final JsonPointer subPath,
final FilterType type, final String operator,
final Object valueAssertion) {
- final Filter filter;
+ return newResultPromise(getLdapFilter0(subPath, type, valueAssertion));
+ }
+
+ private Filter getLdapFilter0(final JsonPointer subPath, final FilterType type, final Object valueAssertion) {
final JsonValue subValue = value.get(subPath);
if (subValue == null) {
- filter = alwaysFalse();
+ return alwaysFalse();
} else if (type == FilterType.PRESENT) {
- filter = alwaysTrue();
+ return alwaysTrue();
} else if (value.isString() && valueAssertion instanceof String) {
final String v1 = toLowerCase(value.asString());
final String v2 = toLowerCase((String) valueAssertion);
switch (type) {
case CONTAINS:
- filter = toFilter(v1.contains(v2));
- break;
+ return toFilter(v1.contains(v2));
case STARTS_WITH:
- filter = toFilter(v1.startsWith(v2));
- break;
+ return toFilter(v1.startsWith(v2));
default:
- filter = compare(type, v1, v2);
- break;
+ return compare(type, v1, v2);
}
} else if (value.isNumber() && valueAssertion instanceof Number) {
final Double v1 = value.asDouble();
final Double v2 = ((Number) valueAssertion).doubleValue();
- filter = compare(type, v1, v2);
+ return compare(type, v1, v2);
} else if (value.isBoolean() && valueAssertion instanceof Boolean) {
final Boolean v1 = value.asBoolean();
final Boolean v2 = (Boolean) valueAssertion;
- filter = compare(type, v1, v2);
+ return compare(type, v1, v2);
} else {
// This property mapper is a candidate but it does not match.
- filter = alwaysFalse();
+ return alwaysFalse();
}
- return newResultPromise(filter);
}
@Override
@@ -148,4 +158,43 @@
return alwaysFalse(); // Not supported.
}
}
+
+ @Override
+ JsonValue toJsonSchema() {
+ return toJsonSchema(value);
+ }
+
+ private static JsonValue toJsonSchema(JsonValue value) {
+ if (value.isMap()) {
+ final JsonValue jsonSchema = json(object(field("type", "object")));
+ final JsonValue jsonProps = json(object());
+ for (String key : value.keys()) {
+ jsonProps.put(key, toJsonSchema(value.get(key)));
+ }
+ jsonSchema.put("properties", jsonSchema);
+ return jsonSchema;
+ } else if (value.isCollection()) {
+ final JsonValue jsonSchema = json(object(field("type", "array")));
+ final JsonValue firstItem = value.get(value.keys().iterator().next());
+ // assume all items have the same schema
+ jsonSchema.put("items", toJsonSchema(firstItem));
+ if (value.isSet()) {
+ jsonSchema.put("uniqueItems", true);
+ }
+ return jsonSchema;
+ } else if (value.isBoolean()) {
+ return json(object(field("type", "boolean"),
+ field("default", value)));
+ } else if (value.isString()) {
+ return json(object(field("type", "string"),
+ field("default", value)));
+ } else if (value.isNumber()) {
+ return json(object(field("type", "number"),
+ field("default", value)));
+ } else if (value.isNull()) {
+ return json(object(field("type", "null")));
+ } else {
+ return null;
+ }
+ }
}
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ObjectPropertyMapper.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ObjectPropertyMapper.java
index 615c7a6..249cfb5 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ObjectPropertyMapper.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ObjectPropertyMapper.java
@@ -17,6 +17,7 @@
import static org.forgerock.opendj.rest2ldap.Rest2Ldap.simple;
import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*;
+import static org.forgerock.json.JsonValue.*;
import static org.forgerock.json.resource.PatchOperation.operation;
import static org.forgerock.opendj.ldap.Filter.alwaysFalse;
import static org.forgerock.opendj.rest2ldap.Rest2Ldap.asResourceException;
@@ -75,11 +76,21 @@
// Nothing to do.
}
+ @Override
+ boolean isRequired() {
+ return false;
+ }
+
+ @Override
+ boolean isMultiValued() {
+ return false;
+ }
+
/**
* Creates an explicit mapping for a property contained in the JSON object. When user attributes are
- * {@link #includeAllUserAttributesByDefault included} by default, be careful to {@link
- * #excludedDefaultUserAttributes exclude} any attributes which have explicit mappings defined using this method,
- * otherwise they will be duplicated in the JSON representation.
+ * {@link #includeAllUserAttributesByDefault(boolean) included} by default, be careful to {@link
+ * #excludedDefaultUserAttributes(Collection) exclude} any attributes which have explicit mappings defined using
+ * this method, otherwise they will be duplicated in the JSON representation.
*
* @param name
* The name of the JSON property to be mapped.
@@ -94,8 +105,8 @@
/**
* Specifies whether all LDAP user attributes should be mapped by default using the default schema based mapping
- * rules. Individual attributes can be excluded using {@link #excludedDefaultUserAttributes} in order to prevent
- * attributes with explicit mappings being mapped twice.
+ * rules. Individual attributes can be excluded using {@link #excludedDefaultUserAttributes(Collection)} in order
+ * to prevent attributes with explicit mappings being mapped twice.
*
* @param include {@code true} if all LDAP user attributes be mapped by default.
* @return A reference to this property mapper.
@@ -107,8 +118,8 @@
/**
* Specifies zero or more user attributes which will be excluded from the default user attribute mappings when
- * enabled using {@link #includeAllUserAttributesByDefault}. Attributes which have explicit mappings should be
- * excluded in order to prevent duplication.
+ * enabled using {@link #includeAllUserAttributesByDefault(boolean)}. Attributes which have explicit mappings
+ * should be excluded in order to prevent duplication.
*
* @param attributeNames The list of attributes to be excluded.
* @return A reference to this property mapper.
@@ -119,8 +130,8 @@
/**
* Specifies zero or more user attributes which will be excluded from the default user attribute mappings when
- * enabled using {@link #includeAllUserAttributesByDefault}. Attributes which have explicit mappings should be
- * excluded in order to prevent duplication.
+ * enabled using {@link #includeAllUserAttributesByDefault(boolean)}. Attributes which have explicit mappings
+ * should be excluded in order to prevent duplication.
*
* @param attributeNames The list of attributes to be excluded.
* @return A reference to this property mapper.
@@ -427,4 +438,27 @@
return includeAllUserAttributesByDefault
&& (excludedDefaultUserAttributes.isEmpty() || !excludedDefaultUserAttributes.contains(attributeName));
}
+
+ @Override
+ JsonValue toJsonSchema() {
+ final List<String> requiredFields = new ArrayList<>();
+ final JsonValue jsonProps = json(object());
+ for (Mapping mapping : mappings.values()) {
+ final String attribute = mapping.name;
+ PropertyMapper mapper = mapping.mapper;
+ jsonProps.put(attribute, mapper.toJsonSchema());
+ if (mapper.isRequired()) {
+ requiredFields.add(attribute);
+ }
+ }
+
+ final JsonValue jsonSchema = json(object(field("type", "object")));
+ if (!requiredFields.isEmpty()) {
+ jsonSchema.put("required", requiredFields);
+ }
+ if (jsonProps.size() > 0) {
+ jsonSchema.put("properties", jsonProps);
+ }
+ return jsonSchema;
+ }
}
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/PropertyMapper.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/PropertyMapper.java
index d428e09..a496a2b 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/PropertyMapper.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/PropertyMapper.java
@@ -40,6 +40,10 @@
// Nothing to do.
}
+ abstract boolean isRequired();
+
+ abstract boolean isMultiValued();
+
/**
* Maps a JSON value to one or more LDAP attributes, returning a promise
* once the transformation has completed. This method is invoked when a REST
@@ -193,4 +197,6 @@
// TODO: methods for obtaining schema information (e.g. name, description, type information).
// TODO: methods for creating sort controls.
+
+ abstract JsonValue toJsonSchema();
}
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferencePropertyMapper.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferencePropertyMapper.java
index a1496b3..69169a4 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferencePropertyMapper.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferencePropertyMapper.java
@@ -15,14 +15,15 @@
*/
package org.forgerock.opendj.rest2ldap;
+import static org.forgerock.json.JsonValue.*;
import static org.forgerock.opendj.ldap.ResultCode.ADMIN_LIMIT_EXCEEDED;
-import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*;
import static org.forgerock.opendj.ldap.LdapException.newLdapException;
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.Utils.connectionFrom;
-import static org.forgerock.util.Reject.checkNotNull;
import static org.forgerock.opendj.rest2ldap.Utils.newBadRequestException;
+import static org.forgerock.util.Reject.checkNotNull;
import static org.forgerock.util.promise.Promises.newResultPromise;
import java.util.ArrayList;
@@ -67,9 +68,7 @@
* valued LDAP attribute.
*/
public final class ReferencePropertyMapper extends AbstractLdapPropertyMapper<ReferencePropertyMapper> {
- /**
- * The maximum number of candidate references to allow in search filters.
- */
+ /** The maximum number of candidate references to allow in search filters. */
private static final int SEARCH_MAX_CANDIDATES = 1000;
private final DnTemplate baseDnTemplate;
@@ -358,4 +357,16 @@
}
});
}
+
+ @Override
+ JsonValue toJsonSchema() {
+ if (mapper.isMultiValued()) {
+ final JsonValue jsonSchema = json(object(field("type", "array")));
+ jsonSchema.put("items", mapper.toJsonSchema());
+ jsonSchema.put("uniqueItems", true);
+ putWritabilityProperties(jsonSchema);
+ return jsonSchema;
+ }
+ return mapper.toJsonSchema();
+ }
}
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Resource.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Resource.java
index ab76ccd..07421a0 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Resource.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Resource.java
@@ -12,11 +12,18 @@
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2016 ForgeRock AS.
- *
*/
package org.forgerock.opendj.rest2ldap;
import static java.util.Arrays.asList;
+import static org.forgerock.api.enums.CountPolicy.*;
+import static org.forgerock.api.enums.PagingMode.*;
+import static org.forgerock.api.enums.ParameterSource.*;
+import static org.forgerock.api.enums.PatchOperation.*;
+import static org.forgerock.api.enums.Stability.*;
+import static org.forgerock.api.models.VersionedPath.*;
+import static org.forgerock.json.JsonValue.*;
+import static org.forgerock.json.resource.ResourceException.*;
import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.ERR_ABSTRACT_TYPE_IN_CREATE;
import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.ERR_MISSING_TYPE_PROPERTY_IN_CREATE;
import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.ERR_UNRECOGNIZED_RESOURCE_SUPER_TYPE;
@@ -34,6 +41,25 @@
import java.util.Map;
import java.util.Set;
+import org.forgerock.api.enums.CreateMode;
+import org.forgerock.api.enums.QueryType;
+import org.forgerock.api.models.ApiDescription;
+import org.forgerock.api.models.ApiError;
+import org.forgerock.api.models.Create;
+import org.forgerock.api.models.Definitions;
+import org.forgerock.api.models.Delete;
+import org.forgerock.api.models.Errors;
+import org.forgerock.api.models.Items;
+import org.forgerock.api.models.Parameter;
+import org.forgerock.api.models.Patch;
+import org.forgerock.api.models.Paths;
+import org.forgerock.api.models.Query;
+import org.forgerock.api.models.Read;
+import org.forgerock.api.models.Reference;
+import org.forgerock.api.models.Schema;
+import org.forgerock.api.models.Services;
+import org.forgerock.api.models.Update;
+import org.forgerock.http.ApiProducer;
import org.forgerock.i18n.LocalizedIllegalArgumentException;
import org.forgerock.json.JsonPointer;
import org.forgerock.json.JsonValue;
@@ -43,11 +69,33 @@
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.Entry;
import org.forgerock.opendj.ldap.LinkedAttribute;
+import org.forgerock.util.i18n.LocalizableString;
/**
* Defines the characteristics of a resource, including its properties, inheritance, and sub-resources.
*/
public final class Resource {
+ // Commons errors
+ private static final String ERROR_BAD_REQUEST = "frapi:common#/errors/badRequest";
+ private static final String ERROR_FORBIDDEN = "frapi:common#/errors/forbidden";
+ private static final String ERROR_INTERNAL_SERVER_ERROR = "frapi:common#/errors/internalServerError";
+ private static final String ERROR_NOT_FOUND = "frapi:common#/errors/notFound";
+ private static final String ERROR_REQUEST_ENTITY_TOO_LARGE = "frapi:common#/errors/requestEntityTooLarge";
+ private static final String ERROR_REQUEST_TIMEOUT = "frapi:common#/errors/requestTimeout";
+ private static final String ERROR_UNAUTHORIZED = "frapi:common#/errors/unauthorized";
+ private static final String ERROR_UNAVAILABLE = "frapi:common#/errors/unavailable";
+ private static final String ERROR_VERSION_MISMATCH = "frapi:common#/errors/versionMismatch";
+
+ // rest2ldap errors
+ private static final String ERROR_ADMIN_LIMIT_EXCEEDED = "#/errors/adminLimitExceeded";
+ private static final String ERROR_READ_FOUND_MULTIPLE_ENTRIES = "#/errors/readFoundMultipleEntries";
+ private static final String ERROR_PASSWORD_MODIFY_REQUIRES_HTTPS = "#/errors/passwordModifyRequiresHttps";
+ private static final String ERROR_PASSWORD_MODIFY_REQUIRES_AUTHENTICATION = "#/errors/passwordModifyRequiresAuthn";
+
+ /** All fields are queryable, but the directory server may reject some requests (unindexed?). */
+ private static final String ALL_FIELDS = "*";
+
+
/** The resource ID. */
private final String id;
/** {@code true} if only sub-types of this resource can be created. */
@@ -444,4 +492,363 @@
PropertyMapper getPropertyMapper() {
return propertyMapper;
}
+
+ /**
+ * Returns the api description that describes a single instance resource.
+ *
+ * @param isReadOnly
+ * whether the associated resource is read only
+ * @return a new api description that describes a single instance resource.
+ */
+ ApiDescription instanceApi(boolean isReadOnly) {
+ if (allProperties.isEmpty()) {
+ return null;
+ }
+
+ org.forgerock.api.models.Resource.Builder resource = org.forgerock.api.models.Resource.
+ resource()
+ .resourceSchema(schemaRef("#/definitions/" + id))
+ .mvccSupported(isMvccSupported());
+
+ resource.read(readOperation());
+ if (!isReadOnly) {
+ resource.update(updateOperation());
+ resource.patch(patchOperation());
+ for (Action action : supportedActions) {
+ resource.action(actions(action));
+ }
+ }
+
+ return ApiDescription.apiDescription()
+ .id("unused").version("unused")
+ .definitions(definitions())
+ .errors(errors())
+ .build();
+ }
+
+ /**
+ * Returns the api description that describes a collection resource.
+ *
+ * @param isReadOnly
+ * whether the associated resource is read only
+ * @return a new api description that describes a collection resource.
+ */
+ ApiDescription collectionApi(boolean isReadOnly) {
+ org.forgerock.api.models.Resource.Builder resource = org.forgerock.api.models.Resource.
+ resource()
+ .resourceSchema(schemaRef("#/definitions/" + id))
+ .mvccSupported(isMvccSupported());
+
+ resource.items(buildItems(isReadOnly));
+ resource.create(createOperation(CreateMode.ID_FROM_SERVER));
+ resource.query(Query.query()
+ .stability(EVOLVING)
+ .type(QueryType.FILTER)
+ .queryableFields(ALL_FIELDS)
+ .pagingModes(COOKIE, OFFSET)
+ .countPolicies(NONE)
+ .error(errorRef(ERROR_BAD_REQUEST))
+ .error(errorRef(ERROR_UNAUTHORIZED))
+ .error(errorRef(ERROR_FORBIDDEN))
+ .error(errorRef(ERROR_REQUEST_TIMEOUT))
+ .error(errorRef(ERROR_ADMIN_LIMIT_EXCEEDED))
+ .error(errorRef(ERROR_INTERNAL_SERVER_ERROR))
+ .error(errorRef(ERROR_UNAVAILABLE))
+ .build());
+
+ return ApiDescription.apiDescription()
+ .id("unused").version("unused")
+ .definitions(definitions())
+ .services(Services.services()
+ .put(id, resource.build())
+ .build())
+ .paths(getPaths())
+ .errors(errors())
+ .build();
+ }
+
+ private Paths getPaths() {
+ return Paths.paths()
+ // do not put anything in the path to avoid unfortunate string concatenation
+ // also use UNVERSIONED and rely on the router to stamp the version
+ .put("", versionedPath().put(UNVERSIONED, resourceRef("#/services/" + id)).build())
+ .build();
+ }
+
+ private Definitions definitions() {
+ final Definitions.Builder definitions = Definitions.definitions();
+ definitions.put(id, buildJsonSchema());
+ for (Resource subType : subTypes) {
+ definitions.put(subType.id, subType.buildJsonSchema());
+ }
+ return definitions.build();
+ }
+
+ /**
+ * Returns the api description that describes a resource with sub resources.
+ *
+ * @param producer
+ * the api producer
+ * @return a new api description that describes a resource with sub resources.
+ */
+ ApiDescription subResourcesApi(ApiProducer<ApiDescription> producer) {
+ return subResourceRouter.api(producer);
+ }
+
+ private boolean isMvccSupported() {
+ return allProperties.containsKey("_rev");
+ }
+
+ private Items buildItems(boolean isReadOnly) {
+ final Items.Builder builder = Items.items();
+ builder.pathParameter(Parameter
+ .parameter()
+ .name("id")
+ .type("string")
+ .source(PATH)
+ .required(true)
+ .build())
+ .read(readOperation());
+ if (!isReadOnly) {
+ builder.create(createOperation(CreateMode.ID_FROM_CLIENT));
+ builder.update(updateOperation());
+ builder.delete(deleteOperation());
+ builder.patch(patchOperation());
+ for (Action action : supportedActions) {
+ builder.action(actions(action));
+ }
+ }
+ return builder.build();
+ }
+
+ private org.forgerock.api.models.Action actions(Action action) {
+ switch (action) {
+ case MODIFY_PASSWORD:
+ return modifyPasswordAction();
+ case RESET_PASSWORD:
+ return resetPasswordAction();
+ default:
+ throw new RuntimeException("Not implemented for action " + action);
+ }
+ }
+
+ private static Create createOperation(CreateMode createMode) {
+ return Create.create()
+ .stability(EVOLVING)
+ .mode(createMode)
+ .error(errorRef(ERROR_BAD_REQUEST))
+ .error(errorRef(ERROR_UNAUTHORIZED))
+ .error(errorRef(ERROR_FORBIDDEN))
+ .error(errorRef(ERROR_NOT_FOUND))
+ .error(errorRef(ERROR_REQUEST_TIMEOUT))
+ .error(errorRef(ERROR_VERSION_MISMATCH))
+ .error(errorRef(ERROR_REQUEST_ENTITY_TOO_LARGE))
+ .error(errorRef(ERROR_ADMIN_LIMIT_EXCEEDED))
+ .error(errorRef(ERROR_INTERNAL_SERVER_ERROR))
+ .error(errorRef(ERROR_UNAVAILABLE))
+ .build();
+ }
+
+ private static Delete deleteOperation() {
+ return Delete.delete()
+ .stability(EVOLVING)
+ .error(errorRef(ERROR_BAD_REQUEST))
+ .error(errorRef(ERROR_UNAUTHORIZED))
+ .error(errorRef(ERROR_FORBIDDEN))
+ .error(errorRef(ERROR_NOT_FOUND))
+ .error(errorRef(ERROR_REQUEST_TIMEOUT))
+ .error(errorRef(ERROR_VERSION_MISMATCH))
+ .error(errorRef(ERROR_REQUEST_ENTITY_TOO_LARGE))
+ .error(errorRef(ERROR_READ_FOUND_MULTIPLE_ENTRIES))
+ .error(errorRef(ERROR_ADMIN_LIMIT_EXCEEDED))
+ .error(errorRef(ERROR_INTERNAL_SERVER_ERROR))
+ .error(errorRef(ERROR_UNAVAILABLE))
+ .build();
+ }
+
+ private static Patch patchOperation() {
+ return Patch.patch()
+ .stability(EVOLVING)
+ .operations(ADD, REMOVE, REPLACE, INCREMENT)
+ .error(errorRef(ERROR_BAD_REQUEST))
+ .error(errorRef(ERROR_UNAUTHORIZED))
+ .error(errorRef(ERROR_FORBIDDEN))
+ .error(errorRef(ERROR_NOT_FOUND))
+ .error(errorRef(ERROR_REQUEST_TIMEOUT))
+ .error(errorRef(ERROR_VERSION_MISMATCH))
+ .error(errorRef(ERROR_REQUEST_ENTITY_TOO_LARGE))
+ .error(errorRef(ERROR_READ_FOUND_MULTIPLE_ENTRIES))
+ .error(errorRef(ERROR_ADMIN_LIMIT_EXCEEDED))
+ .error(errorRef(ERROR_INTERNAL_SERVER_ERROR))
+ .error(errorRef(ERROR_UNAVAILABLE))
+ .build();
+ }
+
+ private static Read readOperation() {
+ return Read.read()
+ .stability(EVOLVING)
+ .error(errorRef(ERROR_BAD_REQUEST))
+ .error(errorRef(ERROR_UNAUTHORIZED))
+ .error(errorRef(ERROR_FORBIDDEN))
+ .error(errorRef(ERROR_NOT_FOUND))
+ .error(errorRef(ERROR_REQUEST_TIMEOUT))
+ .error(errorRef(ERROR_READ_FOUND_MULTIPLE_ENTRIES))
+ .error(errorRef(ERROR_ADMIN_LIMIT_EXCEEDED))
+ .error(errorRef(ERROR_INTERNAL_SERVER_ERROR))
+ .error(errorRef(ERROR_UNAVAILABLE))
+ .build();
+ }
+
+ private static Update updateOperation() {
+ return Update.update()
+ .stability(EVOLVING)
+ .error(errorRef(ERROR_BAD_REQUEST))
+ .error(errorRef(ERROR_UNAUTHORIZED))
+ .error(errorRef(ERROR_FORBIDDEN))
+ .error(errorRef(ERROR_NOT_FOUND))
+ .error(errorRef(ERROR_REQUEST_TIMEOUT))
+ .error(errorRef(ERROR_VERSION_MISMATCH))
+ .error(errorRef(ERROR_REQUEST_ENTITY_TOO_LARGE))
+ .error(errorRef(ERROR_READ_FOUND_MULTIPLE_ENTRIES))
+ .error(errorRef(ERROR_ADMIN_LIMIT_EXCEEDED))
+ .error(errorRef(ERROR_INTERNAL_SERVER_ERROR))
+ .error(errorRef(ERROR_UNAVAILABLE))
+ .build();
+ }
+
+ private static org.forgerock.api.models.Action modifyPasswordAction() {
+ return org.forgerock.api.models.Action.action()
+ .stability(EVOLVING)
+ .name("modifyPassword")
+ .request(passwordModifyRequest())
+ .description("Modify a user password. This action requires HTTPS.")
+ .error(errorRef(ERROR_BAD_REQUEST))
+ .error(errorRef(ERROR_UNAUTHORIZED))
+ .error(errorRef(ERROR_PASSWORD_MODIFY_REQUIRES_HTTPS))
+ .error(errorRef(ERROR_PASSWORD_MODIFY_REQUIRES_AUTHENTICATION))
+ .error(errorRef(ERROR_FORBIDDEN))
+ .error(errorRef(ERROR_NOT_FOUND))
+ .error(errorRef(ERROR_REQUEST_TIMEOUT))
+ .error(errorRef(ERROR_VERSION_MISMATCH))
+ .error(errorRef(ERROR_REQUEST_ENTITY_TOO_LARGE))
+ .error(errorRef(ERROR_READ_FOUND_MULTIPLE_ENTRIES))
+ .error(errorRef(ERROR_ADMIN_LIMIT_EXCEEDED))
+ .error(errorRef(ERROR_INTERNAL_SERVER_ERROR))
+ .error(errorRef(ERROR_UNAVAILABLE))
+ .build();
+ }
+
+ private static org.forgerock.api.models.Schema passwordModifyRequest() {
+ final JsonValue jsonSchema = json(object(
+ field("type", "object"),
+ field("description", "Supply the old password and new password."),
+ field("required", array("oldPassword", "newPassword")),
+ field("properties", object(
+ field("oldPassword", object(
+ field("type", "string"),
+ field("name", "Old Password"),
+ field("description", "Current password as a UTF-8 string."),
+ field("format", "password"))),
+ field("newPassword", object(
+ field("type", "string"),
+ field("name", "New Password"),
+ field("description", "New password as a UTF-8 string."),
+ field("format", "password")))))));
+ return Schema.schema().schema(jsonSchema).build();
+ }
+
+ private static org.forgerock.api.models.Action resetPasswordAction() {
+ return org.forgerock.api.models.Action.action()
+ .stability(EVOLVING)
+ .name("resetPassword")
+ .response(resetPasswordResponse())
+ .description("Reset a user password to a generated value. This action requires HTTPS.")
+ .error(errorRef(ERROR_BAD_REQUEST))
+ .error(errorRef(ERROR_UNAUTHORIZED))
+ .error(errorRef(ERROR_PASSWORD_MODIFY_REQUIRES_HTTPS))
+ .error(errorRef(ERROR_PASSWORD_MODIFY_REQUIRES_AUTHENTICATION))
+ .error(errorRef(ERROR_FORBIDDEN))
+ .error(errorRef(ERROR_NOT_FOUND))
+ .error(errorRef(ERROR_REQUEST_TIMEOUT))
+ .error(errorRef(ERROR_VERSION_MISMATCH))
+ .error(errorRef(ERROR_REQUEST_ENTITY_TOO_LARGE))
+ .error(errorRef(ERROR_READ_FOUND_MULTIPLE_ENTRIES))
+ .error(errorRef(ERROR_ADMIN_LIMIT_EXCEEDED))
+ .error(errorRef(ERROR_INTERNAL_SERVER_ERROR))
+ .error(errorRef(ERROR_UNAVAILABLE))
+ .build();
+ }
+
+ private static org.forgerock.api.models.Schema resetPasswordResponse() {
+ final JsonValue jsonSchema = json(object(
+ field("type", "object"),
+ field("properties", object(
+ field("generatedPassword", object(
+ field("type", "string"),
+ field("description", "Generated password to communicate to the user.")))))));
+ return Schema.schema().schema(jsonSchema).build();
+ }
+
+ private Schema buildJsonSchema() {
+ final List<String> requiredFields = new ArrayList<>();
+ JsonValue properties = json(JsonValue.object());
+ for (Map.Entry<String, PropertyMapper> prop : allProperties.entrySet()) {
+ final String propertyName = prop.getKey();
+ final PropertyMapper mapper = prop.getValue();
+ if (mapper.isRequired()) {
+ requiredFields.add(propertyName);
+ }
+ final JsonValue jsonSchema = mapper.toJsonSchema();
+ if (jsonSchema != null) {
+ properties.put(propertyName, jsonSchema);
+ }
+ }
+
+ final JsonValue jsonSchema = json(object(field("type", "object")));
+ if (!requiredFields.isEmpty()) {
+ jsonSchema.put("required", requiredFields);
+ }
+ if (properties.size() > 0) {
+ jsonSchema.put("properties", properties);
+ }
+ return Schema.schema().schema(jsonSchema).build();
+ }
+
+ private Errors errors() {
+ return Errors
+ .errors()
+ .put("passwordModifyRequiresHttps",
+ error(FORBIDDEN, "Password modify requires a secure connection."))
+ .put("passwordModifyRequiresAuthn",
+ error(FORBIDDEN, "Password modify requires user to be authenticated."))
+ .put("readFoundMultipleEntries",
+ error(INTERNAL_ERROR, "Multiple entries where found when trying to read a single entry."))
+ .put("adminLimitExceeded",
+ error(INTERNAL_ERROR, "The request exceeded an administrative limit."))
+ .build();
+ }
+
+ static ApiError error(int code, String description) {
+ return ApiError.apiError().code(code).description(description).build();
+ }
+
+ static ApiError error(int code, LocalizableString description) {
+ return ApiError.apiError().code(code).description(description).build();
+ }
+
+ static ApiError errorRef(String referenceValue) {
+ return ApiError.apiError().reference(ref(referenceValue)).build();
+ }
+
+ static org.forgerock.api.models.Resource resourceRef(String referenceValue) {
+ return org.forgerock.api.models.Resource.resource().reference(ref(referenceValue)).build();
+ }
+
+ static org.forgerock.api.models.Schema schemaRef(String referenceValue) {
+ return Schema.schema().reference(ref(referenceValue)).build();
+ }
+
+ static Reference ref(String referenceValue) {
+ return Reference.reference().value(referenceValue).build();
+ }
}
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ResourceTypePropertyMapper.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ResourceTypePropertyMapper.java
index 4f79fd8..d667f91 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ResourceTypePropertyMapper.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ResourceTypePropertyMapper.java
@@ -17,6 +17,8 @@
package org.forgerock.opendj.rest2ldap;
import static java.util.Collections.singletonList;
+
+import static org.forgerock.json.JsonValue.*;
import static org.forgerock.opendj.ldap.Filter.alwaysFalse;
import static org.forgerock.opendj.ldap.Filter.alwaysTrue;
import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.ERR_ILLEGAL_FILTER_ASSERTION_VALUE;
@@ -57,6 +59,16 @@
}
@Override
+ boolean isRequired() {
+ return false;
+ }
+
+ @Override
+ boolean isMultiValued() {
+ return false;
+ }
+
+ @Override
Promise<List<Attribute>, ResourceException> create(final Context context,
final Resource resource, final JsonPointer path,
final JsonValue v) {
@@ -120,4 +132,9 @@
return newResultPromise(Collections.<Modification>emptyList());
}
}
+
+ @Override
+ JsonValue toJsonSchema() {
+ return json(object(field("type", "string")));
+ }
}
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2Ldap.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2Ldap.java
index 0ed6cba..ae42e86 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2Ldap.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2Ldap.java
@@ -26,28 +26,17 @@
import java.util.LinkedHashMap;
import java.util.Map;
-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.ForbiddenException;
import org.forgerock.json.resource.InternalServerErrorException;
import org.forgerock.json.resource.NotFoundException;
-import org.forgerock.json.resource.PatchRequest;
import org.forgerock.json.resource.PermanentException;
import org.forgerock.json.resource.PreconditionFailedException;
-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.RequestHandler;
import org.forgerock.json.resource.ResourceException;
-import org.forgerock.json.resource.ResourceResponse;
import org.forgerock.json.resource.RetryableException;
import org.forgerock.json.resource.Router;
import org.forgerock.json.resource.ServiceUnavailableException;
-import org.forgerock.json.resource.UpdateRequest;
import org.forgerock.opendj.ldap.AssertionFailureException;
import org.forgerock.opendj.ldap.AttributeDescription;
import org.forgerock.opendj.ldap.AuthenticationException;
@@ -65,7 +54,6 @@
import org.forgerock.util.Option;
import org.forgerock.util.Options;
import org.forgerock.util.Reject;
-import org.forgerock.util.promise.Promise;
/**
* Provides methods for constructing Rest2Ldap protocol gateways. Applications construct a new Rest2Ldap
@@ -381,44 +369,9 @@
}
private RequestHandler rest2LdapContext(final RequestHandler delegate) {
- return new RequestHandler() {
- public Promise<ActionResponse, ResourceException> handleAction(final Context context,
- final ActionRequest request) {
- return delegate.handleAction(wrap(context), request);
- }
-
- public Promise<ResourceResponse, ResourceException> handleCreate(final Context context,
- final CreateRequest request) {
- return delegate.handleCreate(wrap(context), request);
- }
-
- public Promise<ResourceResponse, ResourceException> handleDelete(final Context context,
- final DeleteRequest request) {
- return delegate.handleDelete(wrap(context), request);
- }
-
- public Promise<ResourceResponse, ResourceException> handlePatch(final Context context,
- final PatchRequest request) {
- return delegate.handlePatch(wrap(context), request);
- }
-
- public Promise<QueryResponse, ResourceException> handleQuery(final Context context,
- final QueryRequest request,
- final QueryResourceHandler handler) {
- return delegate.handleQuery(wrap(context), request, handler);
- }
-
- public Promise<ResourceResponse, ResourceException> handleRead(final Context context,
- final ReadRequest request) {
- return delegate.handleRead(wrap(context), request);
- }
-
- public Promise<ResourceResponse, ResourceException> handleUpdate(final Context context,
- final UpdateRequest request) {
- return delegate.handleUpdate(wrap(context), request);
- }
-
- private Context wrap(final Context context) {
+ return new DescribableRequestHandler(delegate) {
+ @Override
+ protected Context wrap(final Context context) {
return new Rest2LdapContext(context, Rest2Ldap.this);
}
};
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapHttpApplication.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapHttpApplication.java
index fd5ac89..2afe7d0 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapHttpApplication.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapHttpApplication.java
@@ -22,7 +22,6 @@
import static org.forgerock.json.JsonValueFunctions.duration;
import static org.forgerock.json.JsonValueFunctions.enumConstant;
import static org.forgerock.json.JsonValueFunctions.setOf;
-import static org.forgerock.json.resource.http.CrestHttp.newHttpHandler;
import static org.forgerock.opendj.ldap.KeyManagers.useSingleCertificate;
import static org.forgerock.opendj.rest2ldap.Rest2LdapJsonConfigurator.*;
import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*;
@@ -69,7 +68,10 @@
import org.forgerock.i18n.LocalizedIllegalArgumentException;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.json.JsonValue;
+import org.forgerock.json.resource.CrestApplication;
import org.forgerock.json.resource.RequestHandler;
+import org.forgerock.json.resource.Resources;
+import org.forgerock.json.resource.http.CrestHttp;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.ConnectionFactory;
import org.forgerock.opendj.ldap.DN;
@@ -94,6 +96,8 @@
import org.forgerock.util.time.Duration;
import org.forgerock.util.time.TimeService;
+import com.forgerock.opendj.util.ManifestUtil;
+
/** Rest2ldap HTTP application. */
public class Rest2LdapHttpApplication implements HttpApplication {
private static final String DEFAULT_ROOT_FACTORY = "root";
@@ -231,6 +235,27 @@
return configureEndpoints(endpointsDirectory, options);
}
+ private static Handler newHttpHandler(final RequestHandler requestHandler) {
+ final org.forgerock.json.resource.ConnectionFactory factory =
+ Resources.newInternalConnectionFactory(requestHandler);
+ return CrestHttp.newHttpHandler(new CrestApplication() {
+ @Override
+ public org.forgerock.json.resource.ConnectionFactory getConnectionFactory() {
+ return factory;
+ }
+
+ @Override
+ public String getApiId() {
+ return "frapi:opendj:rest2ldap";
+ }
+
+ @Override
+ public String getApiVersion() {
+ return ManifestUtil.getVersionWithRevision("opendj-core");
+ }
+ });
+ }
+
private void configureSecurity(final JsonValue configuration) {
trustManager = configureTrustManager(configuration);
keyManager = configureKeyManager(configuration);
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimplePropertyMapper.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimplePropertyMapper.java
index 2d82561..525e33d 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimplePropertyMapper.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimplePropertyMapper.java
@@ -30,6 +30,8 @@
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.Entry;
import org.forgerock.opendj.ldap.Filter;
+import org.forgerock.opendj.ldap.schema.AttributeType;
+import org.forgerock.opendj.ldap.schema.CoreSchema;
import org.forgerock.services.context.Context;
import org.forgerock.util.Function;
import org.forgerock.util.promise.NeverThrowsException;
@@ -37,6 +39,7 @@
import static java.util.Collections.*;
+import static org.forgerock.json.JsonValue.*;
import static org.forgerock.opendj.ldap.Filter.*;
import static org.forgerock.opendj.rest2ldap.Rest2Ldap.asResourceException;
import static org.forgerock.opendj.rest2ldap.Utils.*;
@@ -198,4 +201,39 @@
return encoder == null ? jsonToByteString(ldapAttributeName) : encoder;
}
+ @Override
+ JsonValue toJsonSchema() {
+ final AttributeType attrType = ldapAttributeName.getAttributeType();
+
+ final JsonValue jsonSchema = json(object(field("type", toJsonSchemaType(attrType))));
+ final String description = attrType.getDescription();
+ if (description != null && !"".equals(description)) {
+ jsonSchema.put("title", description);
+ }
+
+ putWritabilityProperties(jsonSchema);
+ return jsonSchema;
+ }
+
+ private static String toJsonSchemaType(AttributeType attrType) {
+ if (attrType.isPlaceHolder()) {
+ return "string";
+ }
+ // TODO JNR cannot use switch + SchemaConstants.SYNTAX_DIRECTORY_STRING_OID
+ // because the class is not public
+ // this is not nice :(
+ // TODO JNR not so sure about these mappings
+ final String oid = attrType.getSyntax().getOID();
+ if (CoreSchema.getDirectoryStringSyntax().getOID().equals(oid)
+ || CoreSchema.getOctetStringSyntax().getOID().equals(oid)) {
+ return "string";
+ } else if (CoreSchema.getBooleanSyntax().getOID().equals(oid)) {
+ return "boolean";
+ } else if (CoreSchema.getIntegerSyntax().getOID().equals(oid)) {
+ return "integer";
+ } else if (CoreSchema.getNumericStringSyntax().getOID().equals(oid)) {
+ return "number";
+ }
+ return "string";
+ }
}
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResource.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResource.java
index 503bba4..0e61043 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResource.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResource.java
@@ -18,6 +18,8 @@
import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.ERR_UNRECOGNIZED_SUB_RESOURCE_TYPE;
+import org.forgerock.api.models.ApiDescription;
+import org.forgerock.http.ApiProducer;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.LocalizedIllegalArgumentException;
import org.forgerock.json.resource.ActionRequest;
@@ -213,5 +215,10 @@
}
});
}
+
+ @Override
+ public ApiDescription api(ApiProducer<ApiDescription> producer) {
+ return resource.subResourcesApi(producer);
+ }
}
}
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceCollection.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceCollection.java
index bf79ec4..7713ced 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceCollection.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceCollection.java
@@ -28,6 +28,8 @@
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;
@@ -378,6 +380,11 @@
protected <V> Promise<V, ResourceException> handleRequest(final Context context, final Request request) {
return new BadRequestException(ERR_UNSUPPORTED_REQUEST_AGAINST_COLLECTION.get().toString()).asPromise();
}
+
+ @Override
+ public ApiDescription api(ApiProducer<ApiDescription> producer) {
+ return resource.collectionApi(isReadOnly);
+ }
}
/**
@@ -444,5 +451,17 @@
private <T> Function<ResourceException, T, ResourceException> convert404To400() {
return SubResource.convert404To400(ERR_UNSUPPORTED_REQUEST_AGAINST_INSTANCE.get());
}
+
+ /**
+ * Returns {@code null} because the corresponding {@link ApiDescription}
+ * is returned by the {@link CollectionHandler#api(ApiProducer)} method.
+ * <p>
+ * 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<ApiDescription> producer) {
+ return null;
+ }
}
}
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceSingleton.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceSingleton.java
index 81a3b65..77976f2 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceSingleton.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubResourceSingleton.java
@@ -25,6 +25,8 @@
import static org.forgerock.opendj.rest2ldap.RoutingContext.newRoutingContext;
import static org.forgerock.util.promise.Promises.newResultPromise;
+import org.forgerock.api.models.ApiDescription;
+import org.forgerock.http.ApiProducer;
import org.forgerock.json.resource.ActionRequest;
import org.forgerock.json.resource.ActionResponse;
import org.forgerock.json.resource.BadRequestException;
@@ -203,5 +205,10 @@
private <T> Function<ResourceException, T, ResourceException> convert404To400() {
return SubResource.convert404To400(ERR_UNSUPPORTED_REQUEST_AGAINST_SINGLETON.get());
}
+
+ @Override
+ public ApiDescription api(ApiProducer<ApiDescription> producer) {
+ return getResource().instanceApi(isReadOnly);
+ }
}
}
diff --git a/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfiguratorTest.java b/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfiguratorTest.java
new file mode 100644
index 0000000..238f4a6
--- /dev/null
+++ b/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Rest2LdapJsonConfiguratorTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.assertj.core.api.Assertions.*;
+import static org.forgerock.http.util.Json.*;
+import static org.forgerock.json.resource.Requests.*;
+import static org.forgerock.json.resource.ResourcePath.*;
+import static org.forgerock.opendj.rest2ldap.Rest2Ldap.*;
+import static org.forgerock.util.Options.*;
+
+import java.io.File;
+import java.io.StringReader;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+
+import org.forgerock.api.CrestApiProducer;
+import org.forgerock.api.models.ApiDescription;
+import org.forgerock.http.routing.UriRouterContext;
+import org.forgerock.http.util.Json;
+import org.forgerock.json.JsonValue;
+import org.forgerock.json.resource.Request;
+import org.forgerock.json.resource.RequestHandler;
+import org.forgerock.services.context.Context;
+import org.forgerock.services.context.RootContext;
+import org.forgerock.testng.ForgeRockTestCase;
+import org.forgerock.util.Options;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+
+/**
+ * This class tests that the {@link Rest2LdapJsonConfigurator} class can successfully create its
+ * model and generate its API description from the json configuration files.
+ */
+@Test
+@SuppressWarnings("javadoc")
+public class Rest2LdapJsonConfiguratorTest extends ForgeRockTestCase {
+ private static final String ID = "frapi:opendj:rest2ldap";
+ private static final String VERSION = "4.0.0";
+ private static final Path CONFIG_DIR = Paths.get(
+ "../opendj-rest2ldap-servlet/src/main/webapp/WEB-INF/classes/rest2ldap");
+
+ @Test
+ public void testConfigureEndpointsWithApiDescription() throws Exception {
+ final DescribableRequestHandler handler = configureEndpoints(CONFIG_DIR.resolve("endpoints").toFile());
+ final ApiDescription api = requestApi(handler, "api/users/bjensen");
+ assertThat(api).isNotNull();
+
+ // Ensure we can can pretty print and parse back the generated api description
+ parseJson(prettyPrint(api));
+
+ assertThat(api.getId()).isEqualTo(ID);
+ assertThat(api.getVersion()).isEqualTo(VERSION);
+ assertThat(api.getPaths().getNames()).containsOnly("/api/users", "/api/groups");
+ assertThat(api.getDefinitions().getNames()).containsOnly(
+ "frapi:opendj:rest2ldap:group:1.0",
+ "frapi:opendj:rest2ldap:user:1.0",
+ "frapi:opendj:rest2ldap:posixUser:1.0");
+ }
+
+ private DescribableRequestHandler configureEndpoints(final File endpointsDir) throws Exception {
+ final RequestHandler rh = Rest2LdapJsonConfigurator.configureEndpoints(endpointsDir, Options.defaultOptions());
+ DescribableRequestHandler handler = new DescribableRequestHandler(rh);
+ handler.api(new CrestApiProducer(ID, VERSION));
+ return handler;
+ }
+
+ private ApiDescription requestApi(final DescribableRequestHandler handler, String uriPath) {
+ Context context = newRouterContext(uriPath);
+ Request request = newApiRequest(resourcePath(uriPath));
+ return handler.handleApiRequest(context, request);
+ }
+
+ private Context newRouterContext(final String uriPath) {
+ Context ctx = new RootContext();
+ ctx = new Rest2LdapContext(ctx, rest2Ldap(defaultOptions()));
+ ctx = new UriRouterContext(ctx, null, uriPath, Collections.<String, String> emptyMap());
+ return ctx;
+ }
+
+ private String prettyPrint(Object o) throws Exception {
+ final ObjectMapper objectMapper =
+ new ObjectMapper().registerModules(new Json.LocalizableStringModule(), new Json.JsonValueModule());
+ final ObjectWriter writer = objectMapper.writer().withDefaultPrettyPrinter();
+ return writer.writeValueAsString(o);
+ }
+
+ static JsonValue parseJson(final String json) throws Exception {
+ try (StringReader r = new StringReader(json)) {
+ return new JsonValue(readJsonLenient(r));
+ }
+ }
+}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/core/HttpEndpointConfigManager.java b/opendj-server-legacy/src/main/java/org/opends/server/core/HttpEndpointConfigManager.java
index 6d51156..0381a28 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/core/HttpEndpointConfigManager.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/core/HttpEndpointConfigManager.java
@@ -157,7 +157,6 @@
return ccr;
}
- final RouteMatcher<Request> route = newRoute(configuration.getBasePath());
try
{
final HttpApplication application = loadEndpoint(configuration).newHttpApplication();
@@ -170,13 +169,13 @@
{
ccr.setResultCodeIfSuccess(DirectoryServer.getServerErrorResultCode());
ccr.addMessage(ERR_CONFIG_HTTPENDPOINT_UNABLE_TO_START.get(configuration.dn(), stackTraceToSingleLineString(e)));
- router.addRoute(route, ErrorHandler.INTERNAL_SERVER_ERROR);
+ router.addRoute(newRoute(configuration.getBasePath()), ErrorHandler.INTERNAL_SERVER_ERROR);
}
catch (InitializationException | ConfigException ie)
{
ccr.setResultCodeIfSuccess(DirectoryServer.getServerErrorResultCode());
ccr.addMessage(ie.getMessageObject());
- router.addRoute(route, ErrorHandler.INTERNAL_SERVER_ERROR);
+ router.addRoute(newRoute(configuration.getBasePath()), ErrorHandler.INTERNAL_SERVER_ERROR);
}
return ccr;
}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/HTTPConnectionHandler.java b/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/HTTPConnectionHandler.java
index 88ebe1a..9efb145 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/HTTPConnectionHandler.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/HTTPConnectionHandler.java
@@ -42,15 +42,17 @@
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
+import org.forgerock.http.ApiProducer;
+import org.forgerock.http.DescribedHttpApplication;
import org.forgerock.http.Filter;
import org.forgerock.http.Handler;
-import org.forgerock.http.HttpApplication;
import org.forgerock.http.HttpApplicationException;
import org.forgerock.http.handler.Handlers;
import org.forgerock.http.io.Buffer;
import org.forgerock.http.protocol.Request;
import org.forgerock.http.protocol.Response;
import org.forgerock.http.protocol.Status;
+import org.forgerock.http.swagger.SwaggerApiProducer;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.config.server.ConfigChangeResult;
@@ -97,6 +99,8 @@
import org.opends.server.util.SelectableCertificateKeyManager;
import org.opends.server.util.StaticUtils;
+import io.swagger.models.Swagger;
+
/**
* This class defines a connection handler that will be used for communicating
* with clients over HTTP. The connection handler is responsible for
@@ -899,11 +903,11 @@
}
/**
- * This is the root {@link HttpApplication} handling all the requests from the
- * {@link HTTPConnectionHandler}. If accepted, requests are audited and then
- * forwarded to the global {@link ServerContext#getHTTPRouter()}.
+ * This is the root {@link org.forgerock.http.HttpApplication} handling all the requests from the
+ * {@link HTTPConnectionHandler}. If accepted, requests are audited and then forwarded to the
+ * global {@link ServerContext#getHTTPRouter()}.
*/
- private final class RootHttpApplication implements HttpApplication
+ private final class RootHttpApplication implements DescribedHttpApplication
{
@Override
public Handler start() throws HttpApplicationException
@@ -934,6 +938,13 @@
{
return null;
}
+
+ @Override
+ public ApiProducer<Swagger> getApiProducer()
+ {
+ // Needed to enforce generation of CREST APIs
+ return new SwaggerApiProducer(null, null, null);
+ }
}
/** Moves the processing of the request in this Directory Server's worker thread. */
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/Rest2LdapEndpoint.java b/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/Rest2LdapEndpoint.java
index b328186..4334233 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/Rest2LdapEndpoint.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/Rest2LdapEndpoint.java
@@ -15,7 +15,6 @@
*/
package org.opends.server.protocols.http.rest2ldap;
-import static org.forgerock.json.resource.http.CrestHttp.newHttpHandler;
import static org.forgerock.opendj.rest2ldap.Rest2LdapJsonConfigurator.configureEndpoint;
import static org.forgerock.util.Options.defaultOptions;
import static org.opends.messages.ConfigMessages.ERR_CONFIG_REST2LDAP_INVALID;
@@ -32,12 +31,18 @@
import org.forgerock.http.HttpApplicationException;
import org.forgerock.http.io.Buffer;
import org.forgerock.json.JsonValueException;
+import org.forgerock.json.resource.CrestApplication;
+import org.forgerock.json.resource.RequestHandler;
+import org.forgerock.json.resource.Resources;
+import org.forgerock.json.resource.http.CrestHttp;
+import org.forgerock.opendj.rest2ldap.DescribableRequestHandler;
import org.forgerock.opendj.server.config.server.Rest2ldapEndpointCfg;
import org.forgerock.util.Factory;
import org.opends.server.api.HttpEndpoint;
import org.opends.server.core.ServerContext;
import org.opends.server.protocols.http.LocalizedHttpApplicationException;
import org.opends.server.types.InitializationException;
+import org.opends.server.util.BuildVersion;
/**
* Encapsulates configuration required to start a REST2LDAP application embedded
@@ -95,6 +100,27 @@
}
}
+ private Handler newHttpHandler(final RequestHandler requestHandler) {
+ final DescribableRequestHandler handler = new DescribableRequestHandler(requestHandler);
+ final org.forgerock.json.resource.ConnectionFactory factory = Resources.newInternalConnectionFactory(handler);
+ return CrestHttp.newHttpHandler(new CrestApplication() {
+ @Override
+ public org.forgerock.json.resource.ConnectionFactory getConnectionFactory() {
+ return factory;
+ }
+
+ @Override
+ public String getApiId() {
+ return "frapi:opendj:rest2ldap";
+ }
+
+ @Override
+ public String getApiVersion() {
+ return BuildVersion.binaryVersion().toStringNoRevision();
+ }
+ });
+ }
+
@Override
public void stop()
{
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/util/BuildVersion.java b/opendj-server-legacy/src/main/java/org/opends/server/util/BuildVersion.java
index 2312c91..afc63c8 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/util/BuildVersion.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/util/BuildVersion.java
@@ -276,6 +276,17 @@
{
return Utils.joinAsString(".", major, minor, point, rev);
}
+ return toStringNoRevision();
+ }
+
+ /**
+ * Returns a string representation of the BuildVersion including the major, minor and point
+ * versions, but excluding the revision number.
+ *
+ * @return a string representation excluding the revision number
+ */
+ public String toStringNoRevision()
+ {
return Utils.joinAsString(".", major, minor, point);
}
--
Gitblit v1.10.0