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