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