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 RestcProxies}
037 * </ul>
038 */
039public class RemoteMethodMeta {
040
041   private final String httpMethod;
042   private final String fullPath;
043   private final RemoteMethodArg[] pathArgs, queryArgs, headerArgs, formDataArgs;
044   private final RemoteMethodBeanArg[] requestArgs;
045   private final RemoteMethodArg bodyArg;
046   private final RemoteMethodReturn methodReturn;
047   private final Class<?>[] exceptions;
048
049   /**
050    * Constructor.
051    *
052    * @param parentPath The absolute URI of the REST interface backing the interface proxy.
053    * @param m The Java method.
054    * @param defaultMethod The default HTTP method if not specified through annotation.
055    */
056   public RemoteMethodMeta(final String parentPath, Method m, String defaultMethod) {
057      Builder b = new Builder(parentPath, m, defaultMethod);
058      this.httpMethod = b.httpMethod;
059      this.fullPath = b.fullPath;
060      this.pathArgs = b.pathArgs.toArray(new RemoteMethodArg[b.pathArgs.size()]);
061      this.queryArgs = b.queryArgs.toArray(new RemoteMethodArg[b.queryArgs.size()]);
062      this.formDataArgs = b.formDataArgs.toArray(new RemoteMethodArg[b.formDataArgs.size()]);
063      this.headerArgs = b.headerArgs.toArray(new RemoteMethodArg[b.headerArgs.size()]);
064      this.requestArgs = b.requestArgs.toArray(new RemoteMethodBeanArg[b.requestArgs.size()]);
065      this.bodyArg = b.bodyArg;
066      this.methodReturn = b.methodReturn;
067      this.exceptions = m.getExceptionTypes();
068   }
069
070   private static final class Builder {
071      String httpMethod, fullPath, path;
072      List<RemoteMethodArg>
073         pathArgs = new LinkedList<>(),
074         queryArgs = new LinkedList<>(),
075         headerArgs = new LinkedList<>(),
076         formDataArgs = new LinkedList<>();
077      List<RemoteMethodBeanArg>
078         requestArgs = new LinkedList<>();
079      RemoteMethodArg bodyArg;
080      RemoteMethodReturn methodReturn;
081
082      @SuppressWarnings("deprecation")
083      Builder(String parentPath, Method m, String defaultMethod) {
084
085         MethodInfo mi = MethodInfo.of(m);
086
087         org.apache.juneau.rest.client.remote.RemoteMethod orm = mi.getLastAnnotation(org.apache.juneau.rest.client.remote.RemoteMethod.class);
088         if (orm == null)
089            orm = mi.getResolvedReturnType().getLastAnnotation(org.apache.juneau.rest.client.remote.RemoteMethod.class);
090         RemoteMethod rm = mi.getLastAnnotation(RemoteMethod.class);
091         if (rm == null)
092            rm = mi.getResolvedReturnType().getLastAnnotation(RemoteMethod.class);
093
094         httpMethod = rm == null ? (orm == null ? "" : orm.method()) : rm.method();
095         path = rm == null ? (orm == null ? "" : orm.path()) : rm.path();
096
097         if (path.isEmpty()) {
098            path = HttpUtils.detectHttpPath(m, true);
099         }
100         if (httpMethod.isEmpty())
101            httpMethod = HttpUtils.detectHttpMethod(m, true, defaultMethod);
102
103         path = trimSlashes(path);
104
105         if (! isOneOf(httpMethod, "DELETE", "GET", "POST", "PUT", "OPTIONS", "HEAD", "CONNECT", "TRACE", "PATCH"))
106            throw new RemoteMetadataException(m,
107               "Invalid value specified for @RemoteMethod(httpMethod) annotation.  Valid values are [DELTE,GET,POST,PUT,OPTIONS,HEAD,CONNECT,TRACE,PATCH].");
108
109         methodReturn = new RemoteMethodReturn(mi);
110
111         fullPath = path.indexOf("://") != -1 ? path : (parentPath.isEmpty() ? urlEncodePath(path) : (trimSlashes(parentPath) + '/' + urlEncodePath(path)));
112
113         for (ParamInfo mpi : mi.getParams()) {
114            RemoteMethodArg rma = RemoteMethodArg.create(mpi);
115            if (rma != null) {
116               HttpPartType pt = rma.getPartType();
117               if (pt == HEADER)
118                  headerArgs.add(rma);
119               else if (pt == QUERY)
120                  queryArgs.add(rma);
121               else if (pt == FORMDATA)
122                  formDataArgs.add(rma);
123               else if (pt == PATH)
124                  pathArgs.add(rma);
125               else
126                  bodyArg = rma;
127            }
128            RequestBeanMeta rmba = RequestBeanMeta.create(mpi, PropertyStore.DEFAULT);
129            if (rmba != null) {
130               requestArgs.add(new RemoteMethodBeanArg(mpi.getIndex(), rmba));
131            }
132         }
133      }
134   }
135
136   /**
137    * Returns the value of the {@link RemoteMethod#method() @RemoteMethod(httpMethod)} annotation on this Java method.
138    *
139    * @return The value of the annotation, never <jk>null</jk>.
140    */
141   public String getHttpMethod() {
142      return httpMethod;
143   }
144
145   /**
146    * Returns the absolute URI of the REST interface invoked by this Java method.
147    *
148    * @return The absolute URI of the REST interface, never <jk>null</jk>.
149    */
150   public String getFullPath() {
151      return fullPath;
152   }
153
154   /**
155    * Returns the {@link Path @Path} annotated arguments on this Java method.
156    *
157    * @return A map of {@link Path#value() @Path(value)} names to zero-indexed argument indices.
158    */
159   public RemoteMethodArg[] getPathArgs() {
160      return pathArgs;
161   }
162
163   /**
164    * Returns the {@link Query @Query} annotated arguments on this Java method.
165    *
166    * @return A map of {@link Query#value() @Query(value)} names to zero-indexed argument indices.
167    */
168   public RemoteMethodArg[] getQueryArgs() {
169      return queryArgs;
170   }
171
172   /**
173    * Returns the {@link FormData @FormData} annotated arguments on this Java method.
174    *
175    * @return A map of {@link FormData#value() @FormData(value)} names to zero-indexed argument indices.
176    */
177   public RemoteMethodArg[] getFormDataArgs() {
178      return formDataArgs;
179   }
180
181   /**
182    * Returns the {@link Header @Header} annotated arguments on this Java method.
183    *
184    * @return A map of {@link Header#value() @Header(value)} names to zero-indexed argument indices.
185    */
186   public RemoteMethodArg[] getHeaderArgs() {
187      return headerArgs;
188   }
189
190   /**
191    * Returns the {@link Request @Request} annotated arguments on this Java method.
192    *
193    * @return A list of zero-indexed argument indices.
194    */
195   public RemoteMethodBeanArg[] getRequestArgs() {
196      return requestArgs;
197   }
198
199   /**
200    * Returns the argument annotated with {@link Body @Body}.
201    *
202    * @return A index of the argument with the {@link Body @Body} annotation, or <jk>null</jk> if no argument exists.
203    */
204   public RemoteMethodArg getBodyArg() {
205      return bodyArg;
206   }
207
208   /**
209    * Returns whether the method returns the HTTP response body or status code.
210    *
211    * @return Whether the method returns the HTTP response body or status code.
212    */
213   public RemoteMethodReturn getReturns() {
214      return methodReturn;
215   }
216
217   /**
218    * Returns the exceptions thrown by this method.
219    *
220    * @return The exceptions thrown by this method.  Never <jk>null</jk>.
221    */
222   public Class<?>[] getExceptions() {
223      return exceptions;
224   }
225}