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