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.http.remote.RemoteMethod;
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      @SuppressWarnings("deprecation")
089      Builder(String parentPath, Method m, boolean useMethodSignatures, String defaultMethod) {
090
091         MethodInfo mi = MethodInfo.of(m);
092
093         org.apache.juneau.rest.client.remote.RemoteMethod orm = mi.getAnnotation(org.apache.juneau.rest.client.remote.RemoteMethod.class);
094         if (orm == null)
095            orm = mi.getResolvedReturnType().getAnnotation(org.apache.juneau.rest.client.remote.RemoteMethod.class);
096         RemoteMethod rm = mi.getAnnotation(RemoteMethod.class);
097         if (rm == null)
098            rm = mi.getResolvedReturnType().getAnnotation(RemoteMethod.class);
099
100         httpMethod = rm == null ? (orm == null ? "" : orm.method()) : rm.method();
101         path = rm == null ? (orm == null ? "" : orm.path()) : rm.path();
102
103         if (path.isEmpty()) {
104            path = HttpUtils.detectHttpPath(m, ! useMethodSignatures);
105            if (useMethodSignatures)
106               path += HttpUtils.getMethodArgsSignature(m, true);
107         }
108         if (httpMethod.isEmpty())
109            httpMethod = HttpUtils.detectHttpMethod(m, ! useMethodSignatures, defaultMethod);
110
111         path = trimSlashes(path);
112
113         if (! isOneOf(httpMethod, "DELETE", "GET", "POST", "PUT", "OPTIONS", "HEAD", "CONNECT", "TRACE", "PATCH"))
114            throw new RemoteMetadataException(m,
115               "Invalid value specified for @RemoteMethod(httpMethod) annotation.  Valid values are [DELTE,GET,POST,PUT].");
116
117         methodReturn = new RemoteMethodReturn(mi);
118
119         fullPath = path.indexOf("://") != -1 ? path : (parentPath.isEmpty() ? urlEncodePath(path) : (trimSlashes(parentPath) + '/' + urlEncodePath(path)));
120
121         for (ParamInfo mpi : mi.getParams()) {
122            RemoteMethodArg rma = RemoteMethodArg.create(mpi);
123            boolean annotated = false;
124            if (rma != null) {
125               annotated = true;
126               HttpPartType pt = rma.getPartType();
127               if (pt == HEADER)
128                  headerArgs.add(rma);
129               else if (pt == QUERY)
130                  queryArgs.add(rma);
131               else if (pt == FORMDATA)
132                  formDataArgs.add(rma);
133               else if (pt == PATH)
134                  pathArgs.add(rma);
135               else if (pt == BODY)
136                  bodyArg = rma;
137               else
138                  annotated = false;
139            }
140            RequestBeanMeta rmba = RequestBeanMeta.create(mpi, PropertyStore.DEFAULT);
141            if (rmba != null) {
142               annotated = true;
143               requestArgs.add(new RemoteMethodBeanArg(mpi.getIndex(), null, rmba));
144            }
145            if (! annotated) {
146               otherArgs.add(new RemoteMethodArg(mpi.getIndex(), BODY, null));
147            }
148         }
149      }
150   }
151
152   /**
153    * Returns the value of the {@link RemoteMethod#method() @RemoteMethod(httpMethod)} annotation on this Java method.
154    *
155    * @return The value of the annotation, never <jk>null</jk>.
156    */
157   public String getHttpMethod() {
158      return httpMethod;
159   }
160
161   /**
162    * Returns the absolute URL of the REST interface invoked by this Java method.
163    *
164    * @return The absolute URL of the REST interface, never <jk>null</jk>.
165    */
166   public String getFullPath() {
167      return fullPath;
168   }
169
170   /**
171    * Returns the {@link Path @Path} annotated arguments on this Java method.
172    *
173    * @return A map of {@link Path#value() @Path(value)} names to zero-indexed argument indices.
174    */
175   public RemoteMethodArg[] getPathArgs() {
176      return pathArgs;
177   }
178
179   /**
180    * Returns the {@link Query @Query} annotated arguments on this Java method.
181    *
182    * @return A map of {@link Query#value() @Query(value)} names to zero-indexed argument indices.
183    */
184   public RemoteMethodArg[] getQueryArgs() {
185      return queryArgs;
186   }
187
188   /**
189    * Returns the {@link FormData @FormData} annotated arguments on this Java method.
190    *
191    * @return A map of {@link FormData#value() @FormData(value)} names to zero-indexed argument indices.
192    */
193   public RemoteMethodArg[] getFormDataArgs() {
194      return formDataArgs;
195   }
196
197   /**
198    * Returns the {@link Header @Header} annotated arguments on this Java method.
199    *
200    * @return A map of {@link Header#value() @Header(value)} names to zero-indexed argument indices.
201    */
202   public RemoteMethodArg[] getHeaderArgs() {
203      return headerArgs;
204   }
205
206   /**
207    * Returns the {@link Request @Request} annotated arguments on this Java method.
208    *
209    * @return A list of zero-indexed argument indices.
210    */
211   public RemoteMethodBeanArg[] getRequestArgs() {
212      return requestArgs;
213   }
214
215   /**
216    * Returns the remaining non-annotated arguments on this Java method.
217    *
218    * @return A list of zero-indexed argument indices.
219    */
220   public RemoteMethodArg[] getOtherArgs() {
221      return otherArgs;
222   }
223
224   /**
225    * Returns the argument annotated with {@link Body @Body}.
226    *
227    * @return A index of the argument with the {@link Body @Body} annotation, or <jk>null</jk> if no argument exists.
228    */
229   public RemoteMethodArg getBodyArg() {
230      return bodyArg;
231   }
232
233   /**
234    * Returns whether the method returns the HTTP response body or status code.
235    *
236    * @return Whether the method returns the HTTP response body or status code.
237    */
238   public RemoteMethodReturn getReturns() {
239      return methodReturn;
240   }
241
242   /**
243    * Returns the HTTP path of this method.
244    *
245    * @return
246    *    The HTTP path of this method relative to the parent interface.
247    *    <br>Never <jk>null</jk>.
248    *    <br>Never has leading or trailing slashes.
249    */
250   public String getPath() {
251      return path;
252   }
253
254   /**
255    * Returns the underlying Java method that this metadata is about.
256    *
257    * @return
258    *    The underlying Java method that this metadata is about.
259    *    <br>Never <jk>null</jk>.
260    */
261   public Method getJavaMethod() {
262      return method;
263   }
264
265   /**
266    * Returns the exceptions thrown by this method.
267    *
268    * @return The exceptions thrown by this method.  Never <jk>null</jk>.
269    */
270   public Class<?>[] getExceptions() {
271      return exceptions;
272   }
273}