View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions copyright [year] [name of copyright owner]".
13   *
14   * Copyright 2015-2017 ForgeRock AS.
15   */
16  
17  package org.forgerock.json.resource;
18  
19  import java.lang.annotation.Annotation;
20  import java.lang.reflect.InvocationTargetException;
21  import java.lang.reflect.Method;
22  import java.util.Arrays;
23  
24  import org.forgerock.services.context.Context;
25  import org.forgerock.api.annotations.Create;
26  import org.forgerock.api.annotations.Patch;
27  import org.forgerock.api.annotations.Query;
28  import org.forgerock.api.annotations.Update;
29  import org.forgerock.util.promise.Promise;
30  
31  /**
32   * Represents an annotated (or conventionally named) CREST method on an object request handler.
33   * <p>
34   * Methods for finding the appropriate annotated methods can be used by the {@link RequestHandler}
35   * implementations for annotated classes. The returned instances can then be used to invoke the
36   * found method.
37   * <p>
38   * If no appropriately annotated method is found, an attempt to invoke that method will result in
39   * it being handled with a {@link NotSupportedException}.
40   */
41  final class AnnotatedMethod {
42      private final static int ABSENT = -1;
43      private final Object requestHandler;
44      private final Method method;
45      private final int idParameter;
46      private final int contextParameter;
47      private final int requestParameter;
48      private final int queryHandlerParameter;
49      private final int numberOfParameters;
50      private final String operation;
51  
52      AnnotatedMethod(String operation, Object requestHandler, Method method, int idParameter, int contextParameter,
53              int requestParameter, int queryHandlerParameter, int numberOfParameters) {
54          this.operation = operation;
55          this.requestHandler = requestHandler;
56          this.method = method;
57          this.idParameter = idParameter;
58          this.contextParameter = contextParameter;
59          this.requestParameter = requestParameter;
60          this.queryHandlerParameter = queryHandlerParameter;
61          this.numberOfParameters = numberOfParameters;
62      }
63  
64      boolean isUsingId() {
65          return idParameter != ABSENT;
66      }
67  
68      <T> Promise<T, ResourceException> invoke(Context context, Request request) {
69          return invoke(context, request, null, null);
70      }
71  
72      <T> Promise<T, ResourceException> invoke(Context context, Request request, String id) {
73          return invoke(context, request, null, id);
74      }
75  
76      <T> Promise<T, ResourceException> invoke(Context context, Request request,
77              QueryResourceHandler queryHandler) {
78          return invoke(context, request, queryHandler, null);
79      }
80  
81      @SuppressWarnings("unchecked")
82      <T> Promise<T, ResourceException> invoke(Context context, Request request,
83              QueryResourceHandler queryHandler, String id) {
84          if (method == null) {
85              if (operation.equalsIgnoreCase("Create")) {
86                  return new CreateNotSupportedException().asPromise();
87              }
88              return new BadRequestException(operation + " not supported").asPromise();
89          }
90          Object[] args = new Object[numberOfParameters];
91          if (idParameter != ABSENT) {
92              args[idParameter] = id;
93          }
94          if (requestParameter != ABSENT) {
95              args[requestParameter] = request;
96          }
97          if (contextParameter != ABSENT) {
98              args[contextParameter] = context;
99          }
100         if (queryHandlerParameter != ABSENT) {
101             args[queryHandlerParameter] = queryHandler;
102         }
103         try {
104             return (Promise<T, ResourceException>) method.invoke(requestHandler, args);
105         } catch (IllegalAccessException e) {
106             throw new IllegalStateException("Cannot access the annotated method: " + method.getName(), e);
107         } catch (InvocationTargetException e) {
108             throw new IllegalStateException("Exception from invocation expected to be handled by promise", e);
109         }
110     }
111 
112     static AnnotatedMethod findMethod(Object requestHandler, Class<? extends Annotation> annotation, boolean needsId) {
113         for (Method method : requestHandler.getClass().getMethods()) {
114             if (method.getAnnotation(annotation) != null) {
115                 AnnotatedMethod checked = checkMethod(annotation, requestHandler, method, needsId);
116                 if (checked != null) {
117                     return checked;
118                 }
119             }
120         }
121         for (Method method : requestHandler.getClass().getMethods()) {
122             if (method.getName().equals(annotation.getSimpleName().toLowerCase())) {
123                 AnnotatedMethod checked = checkMethod(annotation, requestHandler, method, needsId);
124                 if (checked != null) {
125                     return checked;
126                 }
127             }
128         }
129         return new AnnotatedMethod(annotation.getSimpleName(), null, null, ABSENT, ABSENT, ABSENT, ABSENT, ABSENT);
130     }
131 
132     static AnnotatedMethod checkMethod(Class<?> annotation, Object requestHandler, Method method, boolean needsId) {
133         if (Promise.class.equals(method.getReturnType())) {
134             int idParam = ABSENT;
135             int contextParam = ABSENT;
136             int requestParam = ABSENT;
137             int queryHandlerParam = ABSENT;
138             for (int i = 0; i < method.getParameterTypes().length; i++) {
139                 Class<?> type = method.getParameterTypes()[i];
140                 if (String.class.equals(type)) {
141                     idParam = i;
142                 } else if (Context.class.equals(type)) {
143                     contextParam = i;
144                 } else if (Request.class.isAssignableFrom(type)) {
145                     requestParam = i;
146                 } else if (type.isAssignableFrom(QueryResourceHandler.class)) {
147                     queryHandlerParam = i;
148                 }
149             }
150             if (Arrays.asList(Create.class, Update.class, Patch.class, Query.class).contains(annotation)
151                     && requestParam == ABSENT) {
152                 return null;
153             }
154             if (queryHandlerParam == ABSENT && Query.class.equals(annotation)
155                     || queryHandlerParam != ABSENT && !Query.class.equals(annotation)) {
156                 return null;
157             }
158             if (!needsId || idParam != ABSENT) {
159                 return new AnnotatedMethod(annotation.getSimpleName(), requestHandler, method, idParam, contextParam,
160                         requestParam, queryHandlerParam, method.getParameterTypes().length);
161             }
162         }
163         return null;
164     }
165 }