AnnotatedMethod.java
/*
* 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 2015-2017 ForgeRock AS.
*/
package org.forgerock.json.resource;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.forgerock.services.context.Context;
import org.forgerock.api.annotations.Create;
import org.forgerock.api.annotations.Patch;
import org.forgerock.api.annotations.Query;
import org.forgerock.api.annotations.Update;
import org.forgerock.util.promise.Promise;
/**
* Represents an annotated (or conventionally named) CREST method on an object request handler.
* <p>
* Methods for finding the appropriate annotated methods can be used by the {@link RequestHandler}
* implementations for annotated classes. The returned instances can then be used to invoke the
* found method.
* <p>
* If no appropriately annotated method is found, an attempt to invoke that method will result in
* it being handled with a {@link NotSupportedException}.
*/
final class AnnotatedMethod {
private final static int ABSENT = -1;
private final Object requestHandler;
private final Method method;
private final int idParameter;
private final int contextParameter;
private final int requestParameter;
private final int queryHandlerParameter;
private final int numberOfParameters;
private final String operation;
AnnotatedMethod(String operation, Object requestHandler, Method method, int idParameter, int contextParameter,
int requestParameter, int queryHandlerParameter, int numberOfParameters) {
this.operation = operation;
this.requestHandler = requestHandler;
this.method = method;
this.idParameter = idParameter;
this.contextParameter = contextParameter;
this.requestParameter = requestParameter;
this.queryHandlerParameter = queryHandlerParameter;
this.numberOfParameters = numberOfParameters;
}
boolean isUsingId() {
return idParameter != ABSENT;
}
<T> Promise<T, ResourceException> invoke(Context context, Request request) {
return invoke(context, request, null, null);
}
<T> Promise<T, ResourceException> invoke(Context context, Request request, String id) {
return invoke(context, request, null, id);
}
<T> Promise<T, ResourceException> invoke(Context context, Request request,
QueryResourceHandler queryHandler) {
return invoke(context, request, queryHandler, null);
}
@SuppressWarnings("unchecked")
<T> Promise<T, ResourceException> invoke(Context context, Request request,
QueryResourceHandler queryHandler, String id) {
if (method == null) {
if (operation.equalsIgnoreCase("Create")) {
return new CreateNotSupportedException().asPromise();
}
return new BadRequestException(operation + " not supported").asPromise();
}
Object[] args = new Object[numberOfParameters];
if (idParameter != ABSENT) {
args[idParameter] = id;
}
if (requestParameter != ABSENT) {
args[requestParameter] = request;
}
if (contextParameter != ABSENT) {
args[contextParameter] = context;
}
if (queryHandlerParameter != ABSENT) {
args[queryHandlerParameter] = queryHandler;
}
try {
return (Promise<T, ResourceException>) method.invoke(requestHandler, args);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Cannot access the annotated method: " + method.getName(), e);
} catch (InvocationTargetException e) {
throw new IllegalStateException("Exception from invocation expected to be handled by promise", e);
}
}
static AnnotatedMethod findMethod(Object requestHandler, Class<? extends Annotation> annotation, boolean needsId) {
for (Method method : requestHandler.getClass().getMethods()) {
if (method.getAnnotation(annotation) != null) {
AnnotatedMethod checked = checkMethod(annotation, requestHandler, method, needsId);
if (checked != null) {
return checked;
}
}
}
for (Method method : requestHandler.getClass().getMethods()) {
if (method.getName().equals(annotation.getSimpleName().toLowerCase())) {
AnnotatedMethod checked = checkMethod(annotation, requestHandler, method, needsId);
if (checked != null) {
return checked;
}
}
}
return new AnnotatedMethod(annotation.getSimpleName(), null, null, ABSENT, ABSENT, ABSENT, ABSENT, ABSENT);
}
static AnnotatedMethod checkMethod(Class<?> annotation, Object requestHandler, Method method, boolean needsId) {
if (Promise.class.equals(method.getReturnType())) {
int idParam = ABSENT;
int contextParam = ABSENT;
int requestParam = ABSENT;
int queryHandlerParam = ABSENT;
for (int i = 0; i < method.getParameterTypes().length; i++) {
Class<?> type = method.getParameterTypes()[i];
if (String.class.equals(type)) {
idParam = i;
} else if (Context.class.equals(type)) {
contextParam = i;
} else if (Request.class.isAssignableFrom(type)) {
requestParam = i;
} else if (type.isAssignableFrom(QueryResourceHandler.class)) {
queryHandlerParam = i;
}
}
if (Arrays.asList(Create.class, Update.class, Patch.class, Query.class).contains(annotation)
&& requestParam == ABSENT) {
return null;
}
if (queryHandlerParam == ABSENT && Query.class.equals(annotation)
|| queryHandlerParam != ABSENT && !Query.class.equals(annotation)) {
return null;
}
if (!needsId || idParam != ABSENT) {
return new AnnotatedMethod(annotation.getSimpleName(), requestHandler, method, idParam, contextParam,
requestParam, queryHandlerParam, method.getParameterTypes().length);
}
}
return null;
}
}