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.httppart.HttpPartType.*;
016import static org.apache.juneau.common.internal.StringUtils.*;
017import static org.apache.juneau.http.remote.RemoteUtils.*;
018
019import java.lang.reflect.*;
020import java.util.*;
021import java.util.function.*;
022
023import org.apache.juneau.*;
024import org.apache.juneau.http.annotation.*;
025import org.apache.juneau.http.remote.RemoteOp;
026import org.apache.juneau.httppart.*;
027import org.apache.juneau.httppart.bean.*;
028import org.apache.juneau.internal.*;
029import org.apache.juneau.reflect.*;
030
031/**
032 * Contains the meta-data about a Java method on a REST proxy class.
033 *
034 * <p>
035 * Captures the information in {@link RemoteOp @RemoteOp} annotations for caching and reuse.
036 *
037 * <h5 class='section'>See Also:</h5><ul>
038 *    <li class='link'><a class="doclink" href="../../../../../../index.html#jrc.Proxies">REST Proxies</a>
039 *    <li class='link'><a class="doclink" href="../../../../../../index.html#juneau-rest-client">juneau-rest-client</a>
040 * </ul>
041 */
042public class RemoteOperationMeta {
043
044   private final String httpMethod;
045   private final String fullPath;
046   private final RemoteOperationArg[] pathArgs, queryArgs, headerArgs, formDataArgs;
047   private final RemoteOperationBeanArg[] requestArgs;
048   private final RemoteOperationArg contentArg;
049   private final RemoteOperationReturn methodReturn;
050   private final Class<?>[] exceptions;
051
052   /**
053    * Constructor.
054    *
055    * @param parentPath The absolute URI of the REST interface backing the interface proxy.
056    * @param m The Java method.
057    * @param defaultMethod The default HTTP method if not specified through annotation.
058    */
059   public RemoteOperationMeta(final String parentPath, Method m, String defaultMethod) {
060      Builder b = new Builder(parentPath, m, defaultMethod);
061      this.httpMethod = b.httpMethod;
062      this.fullPath = b.fullPath;
063      this.pathArgs = b.pathArgs.toArray(new RemoteOperationArg[b.pathArgs.size()]);
064      this.queryArgs = b.queryArgs.toArray(new RemoteOperationArg[b.queryArgs.size()]);
065      this.formDataArgs = b.formDataArgs.toArray(new RemoteOperationArg[b.formDataArgs.size()]);
066      this.headerArgs = b.headerArgs.toArray(new RemoteOperationArg[b.headerArgs.size()]);
067      this.requestArgs = b.requestArgs.toArray(new RemoteOperationBeanArg[b.requestArgs.size()]);
068      this.contentArg = b.bodyArg;
069      this.methodReturn = b.methodReturn;
070      this.exceptions = m.getExceptionTypes();
071   }
072
073   private static final class Builder {
074      String httpMethod, fullPath, path;
075      List<RemoteOperationArg>
076         pathArgs = new LinkedList<>(),
077         queryArgs = new LinkedList<>(),
078         headerArgs = new LinkedList<>(),
079         formDataArgs = new LinkedList<>();
080      List<RemoteOperationBeanArg>
081         requestArgs = new LinkedList<>();
082      RemoteOperationArg bodyArg;
083      RemoteOperationReturn methodReturn;
084
085      Builder(String parentPath, Method m, String defaultMethod) {
086
087         MethodInfo mi = MethodInfo.of(m);
088
089         AnnotationList al = mi.getAnnotationList(REMOTE_OP_GROUP);
090         if (al.isEmpty())
091            al = mi.getReturnType().unwrap(Value.class,Optional.class).getAnnotationList(REMOTE_OP_GROUP);
092
093         Value<String> _httpMethod = Value.empty(), _path = Value.empty();
094         al.stream().map(x -> x.getName().substring(6).toUpperCase()).filter(x -> ! x.equals("OP")).forEach(x -> _httpMethod.set(x));
095         al.forEachValue(String.class, "method", NOT_EMPTY, x -> _httpMethod.set(x.trim().toUpperCase()));
096         al.forEachValue(String.class, "path", NOT_EMPTY, x-> _path.set(x.trim()));
097         httpMethod = _httpMethod.orElse("").trim();
098         path = _path.orElse("").trim();
099
100         Value<String> value = Value.empty();
101         al.forEach(RemoteOp.class, x -> isNotEmpty(x.inner().value().trim()), x -> value.set(x.inner().value().trim()));
102
103         if (value.isPresent()) {
104            String v = value.get();
105            int i = v.indexOf(' ');
106            if (i == -1) {
107               httpMethod = v;
108            } else {
109               httpMethod = v.substring(0, i).trim();
110               path = v.substring(i).trim();
111            }
112         } else {
113            al.forEach(x -> ! x.isType(RemoteOp.class) && isNotEmpty(x.getValue(String.class, "value", NOT_EMPTY).orElse("").trim()),x -> value.set(x.getValue(String.class, "value", NOT_EMPTY).get().trim()));
114            if (value.isPresent())
115               path = value.get();
116         }
117
118         if (path.isEmpty()) {
119            path = HttpUtils.detectHttpPath(m, nullIfEmpty(httpMethod));
120         }
121         if (httpMethod.isEmpty())
122            httpMethod = HttpUtils.detectHttpMethod(m, true, defaultMethod);
123
124         path = trimSlashes(path);
125
126         if (! isOneOf(httpMethod, "DELETE", "GET", "POST", "PUT", "OPTIONS", "HEAD", "CONNECT", "TRACE", "PATCH"))
127            throw new RemoteMetadataException(m,
128               "Invalid value specified for @RemoteOp(httpMethod) annotation: '"+httpMethod+"'.  Valid values are [DELETE,GET,POST,PUT,OPTIONS,HEAD,CONNECT,TRACE,PATCH].");
129
130         methodReturn = new RemoteOperationReturn(mi);
131
132         fullPath = path.indexOf("://") != -1 ? path : (parentPath.isEmpty() ? urlEncodePath(path) : (trimSlashes(parentPath) + '/' + urlEncodePath(path)));
133
134         mi.getParams().forEach(x -> {
135            RemoteOperationArg rma = RemoteOperationArg.create(x);
136            if (rma != null) {
137               HttpPartType pt = rma.getPartType();
138               if (pt == HEADER)
139                  headerArgs.add(rma);
140               else if (pt == QUERY)
141                  queryArgs.add(rma);
142               else if (pt == FORMDATA)
143                  formDataArgs.add(rma);
144               else if (pt == PATH)
145                  pathArgs.add(rma);
146               else
147                  bodyArg = rma;
148            }
149            RequestBeanMeta rmba = RequestBeanMeta.create(x, AnnotationWorkList.create());
150            if (rmba != null) {
151               requestArgs.add(new RemoteOperationBeanArg(x.getIndex(), rmba));
152            }
153         });
154      }
155   }
156
157   /**
158    * Returns the value of the {@link RemoteOp#method() @RemoteOp(method)} annotation on this Java method.
159    *
160    * @return The value of the annotation, never <jk>null</jk>.
161    */
162   public String getHttpMethod() {
163      return httpMethod;
164   }
165
166   /**
167    * Returns the absolute URI of the REST interface invoked by this Java method.
168    *
169    * @return The absolute URI of the REST interface, never <jk>null</jk>.
170    */
171   public String getFullPath() {
172      return fullPath;
173   }
174
175   /**
176    * Performs an action on the {@link Path @Path} annotated arguments on this Java method.
177    *
178    * @param action The action to perform.
179    * @return This object.
180    */
181   public RemoteOperationMeta forEachPathArg(Consumer<RemoteOperationArg> action) {
182      for (RemoteOperationArg a : pathArgs)
183         action.accept(a);
184      return this;
185   }
186
187   /**
188    * Performs an action on the {@link Query @Query} annotated arguments on this Java method.
189    *
190    * @param action The action to perform.
191    * @return This object.
192    */
193   public RemoteOperationMeta forEachQueryArg(Consumer<RemoteOperationArg> action) {
194      for (RemoteOperationArg a : queryArgs)
195         action.accept(a);
196      return this;
197   }
198
199   /**
200    * Performs an action on the {@link FormData @FormData} annotated arguments on this Java method.
201    *
202    * @param action The action to perform.
203    * @return This object.
204    */
205   public RemoteOperationMeta forEachFormDataArg(Consumer<RemoteOperationArg> action) {
206      for (RemoteOperationArg a : formDataArgs)
207         action.accept(a);
208      return this;
209   }
210
211   /**
212    * Performs an action on the {@link Header @Header} annotated arguments on this Java method.
213    *
214    * @param action The action to perform.
215    * @return This object.
216    */
217   public RemoteOperationMeta forEachHeaderArg(Consumer<RemoteOperationArg> action) {
218      for (RemoteOperationArg a : headerArgs)
219         action.accept(a);
220      return this;
221   }
222
223   /**
224    * Performs an action on the {@link Request @Request} annotated arguments on this Java method.
225    *
226    * @param action The action to perform.
227    * @return This object.
228    */
229   public RemoteOperationMeta forEachRequestArg(Consumer<RemoteOperationBeanArg> action) {
230      for (RemoteOperationBeanArg a : requestArgs)
231         action.accept(a);
232      return this;
233   }
234
235   /**
236    * Returns the argument annotated with {@link Content @Content}.
237    *
238    * @return A index of the argument with the {@link Content @Content} annotation, or <jk>null</jk> if no argument exists.
239    */
240   public RemoteOperationArg getContentArg() {
241      return contentArg;
242   }
243
244   /**
245    * Returns whether the method returns the HTTP response body or status code.
246    *
247    * @return Whether the method returns the HTTP response body or status code.
248    */
249   public RemoteOperationReturn getReturns() {
250      return methodReturn;
251   }
252
253   /**
254    * Performs an action on the exceptions thrown by this method.
255    *
256    * @param action The action to perform.
257    * @return This object.
258    */
259   public RemoteOperationMeta forEachException(Consumer<Class<?>> action) {
260      for (Class<?> e : exceptions)
261         action.accept(e);
262      return this;
263   }
264}