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