AnnotatedMethod.java revision 568e7121664e2ad34a90bf97ae9129134a8ce408
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose/*
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose * The contents of this file are subject to the terms of the Common Development and
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose * Distribution License (the License). You may not use this file except in compliance with the
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose * License.
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose *
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose * specific language governing permission and limitations under the License.
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose *
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose * When distributing Covered Software, include this CDDL Header Notice in each file and include
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose * Header, with the fields enclosed by brackets [] replaced by your own identifying
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose * information: "Portions copyright [year] [name of copyright owner]".
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose *
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose * Copyright 2015 ForgeRock AS.
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose */
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bosepackage org.forgerock.openam.http.annotations;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Boseimport static org.forgerock.util.promise.Promises.newResultPromise;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Boseimport java.lang.annotation.Annotation;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Boseimport java.lang.reflect.InvocationTargetException;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Boseimport java.lang.reflect.Method;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Boseimport org.forgerock.http.Context;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Boseimport org.forgerock.http.protocol.Request;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Boseimport org.forgerock.http.protocol.Response;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Boseimport org.forgerock.http.protocol.Status;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Boseimport org.forgerock.json.JsonValue;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Boseimport org.forgerock.util.Function;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Boseimport org.forgerock.util.promise.NeverThrowsException;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Boseimport org.forgerock.util.promise.Promise;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bosepublic class AnnotatedMethod {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose private final Object requestHandler;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose private final Method method;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose private final int contextParameter;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose private final int requestParameter;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose private final int numberOfParameters;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose private final String operation;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose private final Function<Object, Promise<Response, NeverThrowsException>, NeverThrowsException> responseAdapter;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose AnnotatedMethod(String operation, Object requestHandler, Method method, int contextParameter,
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose int requestParameter, int numberOfParameters,
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose Function<Object, Promise<Response, NeverThrowsException>, NeverThrowsException> responseAdapter) {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose this.operation = operation;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose this.requestHandler = requestHandler;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose this.method = method;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose this.contextParameter = contextParameter;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose this.requestParameter = requestParameter;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose this.numberOfParameters = numberOfParameters;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose this.responseAdapter = responseAdapter;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose }
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose Promise<Response, NeverThrowsException> invoke(Context context, Request request) {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose if (method == null) {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose return newResultPromise(createErrorResponse(Status.NOT_IMPLEMENTED, "Not supported"));
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose }
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose Object[] args = new Object[numberOfParameters];
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose if (requestParameter > -1) {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose args[requestParameter] = request;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose }
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose if (contextParameter > -1) {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose args[contextParameter] = context;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose }
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose try {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose Object result = method.invoke(requestHandler, args);
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose return responseAdapter.apply(result);
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose } catch (IllegalAccessException e) {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose throw new IllegalStateException("Cannot access the annotated method: " + method.getName(), e);
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose } catch (InvocationTargetException e) {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose throw new IllegalStateException("Exception from invocation expected to be handled by promise", e);
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose }
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose }
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose private Response createErrorResponse(Status status, String s) {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose return new Response().setStatus(status).setEntity(s);
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose }
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose static AnnotatedMethod findMethod(Object requestHandler, Class<? extends Annotation> annotation) {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose for (Method method : requestHandler.getClass().getMethods()) {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose if (method.getAnnotation(annotation) != null) {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose AnnotatedMethod checked = checkMethod(annotation, requestHandler, method);
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose if (checked != null) {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose return checked;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose }
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose }
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose }
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose for (Method method : requestHandler.getClass().getMethods()) {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose String crestVerb = annotation.getSimpleName().toLowerCase();
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose String methodName = method.getName();
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose if (methodName.equals(crestVerb)) {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose AnnotatedMethod checked = checkMethod(annotation, requestHandler, method);
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose if (checked != null) {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose return checked;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose }
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose }
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose }
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose return new AnnotatedMethod(annotation.getSimpleName(), null, null, -1, -1, -1, null);
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose }
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose static AnnotatedMethod checkMethod(Class<?> annotation, Object requestHandler, Method method) {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose int contextParam = -1;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose int requestParam = -1;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose for (int i = 0; i < method.getParameterTypes().length; i++) {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose Class<?> type = method.getParameterTypes()[i];
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose if (Context.class.equals(type)) {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose contextParam = i;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose } else if (Request.class.isAssignableFrom(type)) {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose requestParam = i;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose }
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose }
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose Function<Object, Promise<Response, NeverThrowsException>, NeverThrowsException> resourceCreator;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose if (Promise.class.equals(method.getReturnType())) {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose resourceCreator = new PromisedResponseCreator();
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose } else if (Response.class.equals(method.getReturnType())) {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose resourceCreator = new Function<Object, Promise<Response,NeverThrowsException>, NeverThrowsException>() {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose @Override
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose public Promise<Response, NeverThrowsException> apply(Object o) throws NeverThrowsException {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose return newResultPromise((Response) o);
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose }
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose };
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose } else {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose resourceCreator = ResponseCreator.forType(method.getReturnType());
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose }
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose return new AnnotatedMethod(annotation.getSimpleName(), requestHandler, method, contextParam,
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose requestParam, method.getParameterTypes().length, resourceCreator);
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose }
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose /**
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose * A function to create a {@link Response} from the generic type of a method that produces response
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose * content.
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose */
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose private static class ResponseCreator implements Function<Object, Promise<Response, NeverThrowsException>, NeverThrowsException> {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose private final Function<Object, Object, NeverThrowsException> entityConverter;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose private ResponseCreator(Function<Object, Object, NeverThrowsException> entityConverter) {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose this.entityConverter = entityConverter;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose }
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose @Override
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose public Promise<Response, NeverThrowsException> apply(Object o) throws IllegalStateException {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose Object content = entityConverter.apply(o);
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose return newResultPromise(
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose new Response()
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose .setEntity(content)
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose .setStatus(content == null ? Status.NO_CONTENT : Status.OK));
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose }
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose /**
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose * Uses reflection to deduce the need for a {@code ResourceCreator}, and return an appropriately created one
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose * if needed.
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose * @param type The type for the {@code Response} entity.
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose * @return A new function.
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose */
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose private static Function<Object, Promise<Response, NeverThrowsException>, NeverThrowsException> forType(Class<?> type) {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose if (String.class.equals(type) || Void.class.equals(type) || byte[].class.equals(type)) {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose return new ResponseCreator(IDENTITY_FUNCTION);
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose } else if (JsonValue.class.equals(type)) {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose return new ResponseCreator(new Function<Object, Object, NeverThrowsException>() {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose @Override
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose public Object apply(Object o) throws NeverThrowsException {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose return ((JsonValue) o).getObject();
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose }
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose });
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose }
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose throw new IllegalArgumentException("Unsupported response type: " + type);
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose }
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose }
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose private static class PromisedResponseCreator implements Function<Object, Promise<Response, NeverThrowsException>, NeverThrowsException> {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose
52f1093ef3d7c44132ec10c57436865b2cbb19d7Sumit Bose @Override
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose public Promise<Response, NeverThrowsException> apply(Object o) throws NeverThrowsException {
52f1093ef3d7c44132ec10c57436865b2cbb19d7Sumit Bose throw new UnsupportedOperationException("to be implemented");
52f1093ef3d7c44132ec10c57436865b2cbb19d7Sumit Bose }
52f1093ef3d7c44132ec10c57436865b2cbb19d7Sumit Bose }
52f1093ef3d7c44132ec10c57436865b2cbb19d7Sumit Bose
52f1093ef3d7c44132ec10c57436865b2cbb19d7Sumit Bose private static final Function<Object, Object, NeverThrowsException> IDENTITY_FUNCTION = new Function<Object, Object, NeverThrowsException>() {
52f1093ef3d7c44132ec10c57436865b2cbb19d7Sumit Bose
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose @Override
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose public Object apply(Object o) throws NeverThrowsException {
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose return null;
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose }
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose };
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose}
885386b7e3f1c3e74b354576b98a092b0835d64eSumit Bose