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