001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.rest.client.remote;
014
015import static org.apache.juneau.internal.StringUtils.*;
016import static org.apache.juneau.httppart.HttpPartType.*;
017
018import java.lang.reflect.*;
019import java.util.*;
020
021import org.apache.juneau.*;
022import org.apache.juneau.http.annotation.*;
023import org.apache.juneau.httppart.*;
024import org.apache.juneau.httppart.bean.*;
025import org.apache.juneau.internal.*;
026
027/**
028 * Contains the meta-data about a Java method on a REST proxy class.
029 *
030 * <p>
031 * Captures the information in {@link RemoteMethod @RemoteMethod} annotations for caching and reuse.
032 *
033 * <h5 class='section'>See Also:</h5>
034 * <ul class='doctree'>
035 *    <li class='link'>{@doc juneau-rest-client.RestProxies}
036 * </ul>
037 */
038public class RemoteMethodMeta {
039
040   private final String httpMethod;
041   private final String fullPath, path;
042   private final RemoteMethodArg[] pathArgs, queryArgs, headerArgs, formDataArgs, otherArgs;
043   private final RemoteMethodBeanArg[] requestArgs;
044   private final RemoteMethodArg bodyArg;
045   private final RemoteMethodReturn methodReturn;
046   private final Method method;
047   private final Class<?>[] exceptions;
048
049   /**
050    * Constructor.
051    *
052    * @param parentPath The absolute URL of the REST interface backing the interface proxy.
053    * @param m The Java method.
054    * @param useMethodSignatures If <jk>true</jk> then the default path for the method should be the full method signature.
055    * @param defaultMethod The default HTTP method if not specified through annotation.
056    */
057   public RemoteMethodMeta(final String parentPath, Method m, boolean useMethodSignatures, String defaultMethod) {
058      Builder b = new Builder(parentPath, m, useMethodSignatures, defaultMethod);
059      this.method = m;
060      this.httpMethod = b.httpMethod;
061      this.path = b.path;
062      this.fullPath = b.fullPath;
063      this.pathArgs = b.pathArgs.toArray(new RemoteMethodArg[b.pathArgs.size()]);
064      this.queryArgs = b.queryArgs.toArray(new RemoteMethodArg[b.queryArgs.size()]);
065      this.formDataArgs = b.formDataArgs.toArray(new RemoteMethodArg[b.formDataArgs.size()]);
066      this.headerArgs = b.headerArgs.toArray(new RemoteMethodArg[b.headerArgs.size()]);
067      this.requestArgs = b.requestArgs.toArray(new RemoteMethodBeanArg[b.requestArgs.size()]);
068      this.otherArgs = b.otherArgs.toArray(new RemoteMethodArg[b.otherArgs.size()]);
069      this.bodyArg = b.bodyArg;
070      this.methodReturn = b.methodReturn;
071      this.exceptions = m.getExceptionTypes();
072   }
073
074   private static final class Builder {
075      String httpMethod, fullPath, path;
076      List<RemoteMethodArg>
077         pathArgs = new LinkedList<>(),
078         queryArgs = new LinkedList<>(),
079         headerArgs = new LinkedList<>(),
080         formDataArgs = new LinkedList<>(),
081         otherArgs = new LinkedList<>();
082      List<RemoteMethodBeanArg>
083         requestArgs = new LinkedList<>();
084      RemoteMethodArg bodyArg;
085      RemoteMethodReturn methodReturn;
086
087      Builder(String parentPath, Method m, boolean useMethodSignatures, String defaultMethod) {
088
089         RemoteMethod rm = m.getAnnotation(RemoteMethod.class);
090
091         httpMethod = rm == null ? "" : rm.method();
092         path = rm == null ? "" : rm.path();
093
094         if (path.isEmpty()) {
095            path = HttpUtils.detectHttpPath(m, ! useMethodSignatures);
096            if (useMethodSignatures)
097               path += HttpUtils.getMethodArgsSignature(m, true);
098         }
099         if (httpMethod.isEmpty())
100            httpMethod = HttpUtils.detectHttpMethod(m, ! useMethodSignatures, defaultMethod);
101
102         path = trimSlashes(path);
103
104         if (! isOneOf(httpMethod, "DELETE", "GET", "POST", "PUT", "OPTIONS", "HEAD", "CONNECT", "TRACE", "PATCH"))
105            throw new RemoteMetadataException(m,
106               "Invalid value specified for @RemoteMethod(httpMethod) annotation.  Valid values are [DELTE,GET,POST,PUT].");
107
108         methodReturn = new RemoteMethodReturn(m);
109
110         fullPath = path.indexOf("://") != -1 ? path : (parentPath.isEmpty() ? urlEncodePath(path) : (trimSlashes(parentPath) + '/' + urlEncodePath(path)));
111
112         for (int i = 0; i < m.getParameterTypes().length; i++) {
113            RemoteMethodArg rma = RemoteMethodArg.create(m, i);
114            boolean annotated = false;
115            if (rma != null) {
116               annotated = true;
117               HttpPartType pt = rma.getPartType();
118               if (pt == HEADER)
119                  headerArgs.add(rma);
120               else if (pt == QUERY)
121                  queryArgs.add(rma);
122               else if (pt == FORMDATA)
123                  formDataArgs.add(rma);
124               else if (pt == PATH)
125                  pathArgs.add(rma);
126               else if (pt == BODY)
127                  bodyArg = rma;
128               else
129                  annotated = false;
130            }
131            RequestBeanMeta rmba = RequestBeanMeta.create(m, i, PropertyStore.DEFAULT);
132            if (rmba != null) {
133               annotated = true;
134               requestArgs.add(new RemoteMethodBeanArg(i, null, rmba));
135            }
136            if (! annotated) {
137               otherArgs.add(new RemoteMethodArg(i, BODY, null));
138            }
139         }
140      }
141   }
142
143   /**
144    * Returns the value of the {@link RemoteMethod#method() @RemoteMethod(httpMethod)} annotation on this Java method.
145    *
146    * @return The value of the annotation, never <jk>null</jk>.
147    */
148   public String getHttpMethod() {
149      return httpMethod;
150   }
151
152   /**
153    * Returns the absolute URL of the REST interface invoked by this Java method.
154    *
155    * @return The absolute URL of the REST interface, never <jk>null</jk>.
156    */
157   public String getFullPath() {
158      return fullPath;
159   }
160
161   /**
162    * Returns the {@link Path @Path} annotated arguments on this Java method.
163    *
164    * @return A map of {@link Path#value() @Path(value)} names to zero-indexed argument indices.
165    */
166   public RemoteMethodArg[] getPathArgs() {
167      return pathArgs;
168   }
169
170   /**
171    * Returns the {@link Query @Query} annotated arguments on this Java method.
172    *
173    * @return A map of {@link Query#value() @Query(value)} names to zero-indexed argument indices.
174    */
175   public RemoteMethodArg[] getQueryArgs() {
176      return queryArgs;
177   }
178
179   /**
180    * Returns the {@link FormData @FormData} annotated arguments on this Java method.
181    *
182    * @return A map of {@link FormData#value() @FormData(value)} names to zero-indexed argument indices.
183    */
184   public RemoteMethodArg[] getFormDataArgs() {
185      return formDataArgs;
186   }
187
188   /**
189    * Returns the {@link Header @Header} annotated arguments on this Java method.
190    *
191    * @return A map of {@link Header#value() @Header(value)} names to zero-indexed argument indices.
192    */
193   public RemoteMethodArg[] getHeaderArgs() {
194      return headerArgs;
195   }
196
197   /**
198    * Returns the {@link Request @Request} annotated arguments on this Java method.
199    *
200    * @return A list of zero-indexed argument indices.
201    */
202   public RemoteMethodBeanArg[] getRequestArgs() {
203      return requestArgs;
204   }
205
206   /**
207    * Returns the remaining non-annotated arguments on this Java method.
208    *
209    * @return A list of zero-indexed argument indices.
210    */
211   public RemoteMethodArg[] getOtherArgs() {
212      return otherArgs;
213   }
214
215   /**
216    * Returns the argument annotated with {@link Body @Body}.
217    *
218    * @return A index of the argument with the {@link Body @Body} annotation, or <jk>null</jk> if no argument exists.
219    */
220   public RemoteMethodArg getBodyArg() {
221      return bodyArg;
222   }
223
224   /**
225    * Returns whether the method returns the HTTP response body or status code.
226    *
227    * @return Whether the method returns the HTTP response body or status code.
228    */
229   public RemoteMethodReturn getReturns() {
230      return methodReturn;
231   }
232
233   /**
234    * Returns the HTTP path of this method.
235    *
236    * @return
237    *    The HTTP path of this method relative to the parent interface.
238    *    <br>Never <jk>null</jk>.
239    *    <br>Never has leading or trailing slashes.
240    */
241   public String getPath() {
242      return path;
243   }
244
245   /**
246    * Returns the underlying Java method that this metadata is about.
247    *
248    * @return
249    *    The underlying Java method that this metadata is about.
250    *    <br>Never <jk>null</jk>.
251    */
252   public Method getJavaMethod() {
253      return method;
254   }
255
256   /**
257    * Returns the exceptions thrown by this method.
258    *
259    * @return The exceptions thrown by this method.  Never <jk>null</jk>.
260    */
261   public Class<?>[] getExceptions() {
262      return exceptions;
263   }
264}