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 > juneau-rest-client > 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}