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.remoteable;
014
015import static org.apache.juneau.internal.ClassUtils.*;
016import static org.apache.juneau.internal.StringUtils.*;
017
018import java.lang.annotation.*;
019import java.lang.reflect.*;
020import java.util.*;
021
022/**
023 * Contains the meta-data about a Java method on a remoteable interface.
024 * 
025 * <p>
026 * Captures the information in {@link RemoteMethod @RemoteMethod} annotations for caching and reuse.
027 * 
028 * <h5 class='section'>See Also:</h5>
029 * <ul class='doctree'>
030 *    <li class='link'><a class='doclink' href='../../../../overview-summary.html#juneau-rest-client.3rdPartyProxies'>Overview &gt; juneau-rest-client &gt; Interface Proxies Against 3rd-party REST Interfaces</a>
031 * </ul>
032 */
033public class RemoteableMethodMeta {
034
035   private final String httpMethod;
036   private final String url;
037   private final RemoteMethodArg[] pathArgs, queryArgs, headerArgs, formDataArgs, requestBeanArgs;
038   private final Integer[] otherArgs;
039   private final Integer bodyArg;
040   private final ReturnValue returnValue;
041
042   /**
043    * Constructor.
044    * 
045    * @param restUrl The absolute URL of the REST interface backing the interface proxy.
046    * @param m The Java method.
047    */
048   public RemoteableMethodMeta(final String restUrl, Method m) {
049      Builder b = new Builder(restUrl, m);
050      this.httpMethod = b.httpMethod;
051      this.url = b.url;
052      this.pathArgs = b.pathArgs.toArray(new RemoteMethodArg[b.pathArgs.size()]);
053      this.queryArgs = b.queryArgs.toArray(new RemoteMethodArg[b.queryArgs.size()]);
054      this.formDataArgs = b.formDataArgs.toArray(new RemoteMethodArg[b.formDataArgs.size()]);
055      this.headerArgs = b.headerArgs.toArray(new RemoteMethodArg[b.headerArgs.size()]);
056      this.requestBeanArgs = b.requestBeanArgs.toArray(new RemoteMethodArg[b.requestBeanArgs.size()]);
057      this.otherArgs = b.otherArgs.toArray(new Integer[b.otherArgs.size()]);
058      this.bodyArg = b.bodyArg;
059      this.returnValue = b.returnValue;
060   }
061
062   private static final class Builder {
063      String httpMethod, url;
064      List<RemoteMethodArg>
065         pathArgs = new LinkedList<>(),
066         queryArgs = new LinkedList<>(),
067         headerArgs = new LinkedList<>(),
068         formDataArgs = new LinkedList<>(),
069         requestBeanArgs = new LinkedList<>();
070      List<Integer>
071         otherArgs = new LinkedList<>();
072      Integer bodyArg;
073      ReturnValue returnValue;
074
075      Builder(String restUrl, Method m) {
076         Remoteable r = m.getDeclaringClass().getAnnotation(Remoteable.class);
077         RemoteMethod rm = m.getAnnotation(RemoteMethod.class);
078
079         httpMethod = rm == null ? "POST" : rm.httpMethod();
080         if (! isOneOf(httpMethod, "DELETE", "GET", "POST", "PUT"))
081            throw new RemoteableMetadataException(m,
082               "Invalid value specified for @RemoteMethod.httpMethod() annotation.  Valid values are [DELTE,GET,POST,PUT].");
083
084         String path = rm == null || rm.path().isEmpty() ? null : rm.path();
085         String methodPaths = r == null ? "NAME" : r.methodPaths();
086
087         if (! isOneOf(methodPaths, "NAME", "SIGNATURE"))
088            throw new RemoteableMetadataException(m,
089               "Invalid value specified for @Remoteable.methodPaths() annotation.  Valid values are [NAME,SIGNATURE].");
090
091         returnValue = rm == null ? ReturnValue.BODY : rm.returns();
092
093         url =
094            trimSlashes(restUrl)
095            + '/'
096            + (path != null ? trimSlashes(path) : urlEncode("NAME".equals(methodPaths) ? m.getName() : getMethodSignature(m)));
097
098         int index = 0;
099         for (Annotation[] aa : m.getParameterAnnotations()) {
100            boolean annotated = false;
101            for (Annotation a : aa) {
102               Class<?> ca = a.annotationType();
103               if (ca == Path.class) {
104                  Path p = (Path)a;
105                  annotated = pathArgs.add(new RemoteMethodArg(p.name(), p.value(), index, false, p.serializer()));
106               } else if (ca == Query.class) {
107                  Query q = (Query)a;
108                  annotated = queryArgs.add(new RemoteMethodArg(q.name(), q.value(), index, q.skipIfEmpty(), q.serializer()));
109               } else if (ca == QueryIfNE.class) {
110                  QueryIfNE q = (QueryIfNE)a;
111                  annotated = queryArgs.add(new RemoteMethodArg(q.name(), q.value(), index, true, q.serializer()));
112               } else if (ca == FormData.class) {
113                  FormData f = (FormData)a;
114                  annotated = formDataArgs.add(new RemoteMethodArg(f.name(), f.value(), index, f.skipIfEmpty(), f.serializer()));
115               } else if (ca == FormDataIfNE.class) {
116                  FormDataIfNE f = (FormDataIfNE)a;
117                  annotated = formDataArgs.add(new RemoteMethodArg(f.name(), f.value(), index, true, f.serializer()));
118               } else if (ca == Header.class) {
119                  Header h = (Header)a;
120                  annotated = headerArgs.add(new RemoteMethodArg(h.name(), h.value(), index, h.skipIfEmpty(), h.serializer()));
121               } else if (ca == HeaderIfNE.class) {
122                  HeaderIfNE h = (HeaderIfNE)a;
123                  annotated = headerArgs.add(new RemoteMethodArg(h.name(), h.value(), index, true, h.serializer()));
124               } else if (ca == RequestBean.class) {
125                  RequestBean rb = (RequestBean)a;
126                  annotated = requestBeanArgs.add(new RemoteMethodArg("", "", index, false, rb.serializer()));
127               } else if (ca == Body.class) {
128                  annotated = true;
129                  if (bodyArg == null)
130                     bodyArg = index;
131                  else
132                     throw new RemoteableMetadataException(m,
133                        "Multiple @Body parameters found.  Only one can be specified per Java method.");
134               }
135            }
136            if (! annotated)
137               otherArgs.add(index);
138            index++;
139         }
140
141         if (bodyArg != null && otherArgs.size() > 0)
142            throw new RemoteableMetadataException(m,
143               "@Body and non-annotated parameters found together.  Non-annotated parameters cannot be used when @Body is used.");
144      }
145   }
146
147   /**
148    * Returns the value of the {@link RemoteMethod#httpMethod() @RemoteMethod.httpMethod()} annotation on this Java method.
149    * 
150    * @return The value of the annotation, never <jk>null</jk>.
151    */
152   public String getHttpMethod() {
153      return httpMethod;
154   }
155
156   /**
157    * Returns the absolute URL of the REST interface invoked by this Java method.
158    * 
159    * @return The absolute URL of the REST interface, never <jk>null</jk>.
160    */
161   public String getUrl() {
162      return url;
163   }
164
165   /**
166    * Returns the {@link Path @Path} annotated arguments on this Java method.
167    * 
168    * @return A map of {@link Path#value() @Path.value()} names to zero-indexed argument indices.
169    */
170   public RemoteMethodArg[] getPathArgs() {
171      return pathArgs;
172   }
173
174   /**
175    * Returns the {@link Query @Query} annotated arguments on this Java method.
176    * 
177    * @return A map of {@link Query#value() @Query.value()} names to zero-indexed argument indices.
178    */
179   public RemoteMethodArg[] getQueryArgs() {
180      return queryArgs;
181   }
182
183   /**
184    * Returns the {@link FormData @FormData} annotated arguments on this Java method.
185    * 
186    * @return A map of {@link FormData#value() @FormData.value()} names to zero-indexed argument indices.
187    */
188   public RemoteMethodArg[] getFormDataArgs() {
189      return formDataArgs;
190   }
191
192   /**
193    * Returns the {@link Header @Header} annotated arguments on this Java method.
194    * 
195    * @return A map of {@link Header#value() @Header.value()} names to zero-indexed argument indices.
196    */
197   public RemoteMethodArg[] getHeaderArgs() {
198      return headerArgs;
199   }
200
201   /**
202    * Returns the {@link RequestBean @RequestBean} annotated arguments on this Java method.
203    * 
204    * @return A list of zero-indexed argument indices.
205    */
206   public RemoteMethodArg[] getRequestBeanArgs() {
207      return requestBeanArgs;
208   }
209
210   /**
211    * Returns the remaining non-annotated arguments on this Java method.
212    * 
213    * @return A list of zero-indexed argument indices.
214    */
215   public Integer[] getOtherArgs() {
216      return otherArgs;
217   }
218
219   /**
220    * Returns the argument annotated with {@link Body @Body}.
221    * 
222    * @return A index of the argument with the {@link Body @Body} annotation, or <jk>null</jk> if no argument exists.
223    */
224   public Integer getBodyArg() {
225      return bodyArg;
226   }
227
228   /**
229    * Returns whether the method returns the HTTP response body or status code.
230    * 
231    * @return Whether the method returns the HTTP response body or status code.
232    */
233   public ReturnValue getReturns() {
234      return returnValue;
235   }
236}