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}