001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.rest.client.remote; 018 019import static org.apache.juneau.common.utils.StringUtils.*; 020import static org.apache.juneau.common.utils.Utils.*; 021import static org.apache.juneau.http.remote.RemoteUtils.*; 022import static org.apache.juneau.httppart.HttpPartType.*; 023 024import java.lang.reflect.*; 025import java.util.*; 026import java.util.function.*; 027 028import org.apache.juneau.*; 029import org.apache.juneau.common.utils.*; 030import org.apache.juneau.http.annotation.*; 031import org.apache.juneau.http.remote.*; 032import org.apache.juneau.httppart.*; 033import org.apache.juneau.httppart.bean.*; 034import org.apache.juneau.internal.*; 035import org.apache.juneau.reflect.*; 036 037/** 038 * Contains the meta-data about a Java method on a REST proxy class. 039 * 040 * <p> 041 * Captures the information in {@link RemoteOp @RemoteOp} annotations for caching and reuse. 042 * 043 * <h5 class='section'>See Also:</h5><ul> 044 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestProxyBasics">REST Proxy Basics</a> 045 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestClientBasics">juneau-rest-client Basics</a> 046 * </ul> 047 */ 048public class RemoteOperationMeta { 049 050 private final String httpMethod; 051 private final String fullPath; 052 private final RemoteOperationArg[] pathArgs, queryArgs, headerArgs, formDataArgs; 053 private final RemoteOperationBeanArg[] requestArgs; 054 private final RemoteOperationArg contentArg; 055 private final RemoteOperationReturn methodReturn; 056 private final Class<?>[] exceptions; 057 058 /** 059 * Constructor. 060 * 061 * @param parentPath The absolute URI of the REST interface backing the interface proxy. 062 * @param m The Java method. 063 * @param defaultMethod The default HTTP method if not specified through annotation. 064 */ 065 public RemoteOperationMeta(final String parentPath, Method m, String defaultMethod) { 066 Builder b = new Builder(parentPath, m, defaultMethod); 067 this.httpMethod = b.httpMethod; 068 this.fullPath = b.fullPath; 069 this.pathArgs = b.pathArgs.toArray(new RemoteOperationArg[b.pathArgs.size()]); 070 this.queryArgs = b.queryArgs.toArray(new RemoteOperationArg[b.queryArgs.size()]); 071 this.formDataArgs = b.formDataArgs.toArray(new RemoteOperationArg[b.formDataArgs.size()]); 072 this.headerArgs = b.headerArgs.toArray(new RemoteOperationArg[b.headerArgs.size()]); 073 this.requestArgs = b.requestArgs.toArray(new RemoteOperationBeanArg[b.requestArgs.size()]); 074 this.contentArg = b.bodyArg; 075 this.methodReturn = b.methodReturn; 076 this.exceptions = m.getExceptionTypes(); 077 } 078 079 private static class Builder { 080 String httpMethod, fullPath, path; 081 List<RemoteOperationArg> 082 pathArgs = new LinkedList<>(), 083 queryArgs = new LinkedList<>(), 084 headerArgs = new LinkedList<>(), 085 formDataArgs = new LinkedList<>(); 086 List<RemoteOperationBeanArg> 087 requestArgs = new LinkedList<>(); 088 RemoteOperationArg bodyArg; 089 RemoteOperationReturn methodReturn; 090 091 Builder(String parentPath, Method m, String defaultMethod) { 092 093 MethodInfo mi = MethodInfo.of(m); 094 095 AnnotationList al = mi.getAnnotationList(REMOTE_OP_GROUP); 096 if (al.isEmpty()) 097 al = mi.getReturnType().unwrap(Value.class,Optional.class).getAnnotationList(REMOTE_OP_GROUP); 098 099 Value<String> _httpMethod = Value.empty(), _path = Value.empty(); 100 al.stream().map(x -> x.getName().substring(6).toUpperCase()).filter(x -> ! x.equals("OP")).forEach(x -> _httpMethod.set(x)); 101 al.forEachValue(String.class, "method", NOT_EMPTY, x -> _httpMethod.set(x.trim().toUpperCase())); 102 al.forEachValue(String.class, "path", NOT_EMPTY, x-> _path.set(x.trim())); 103 httpMethod = _httpMethod.orElse("").trim(); 104 path = _path.orElse("").trim(); 105 106 Value<String> value = Value.empty(); 107 al.forEach(RemoteOp.class, x -> isNotEmpty(x.inner().value().trim()), x -> value.set(x.inner().value().trim())); 108 109 if (value.isPresent()) { 110 String v = value.get(); 111 int i = v.indexOf(' '); 112 if (i == -1) { 113 httpMethod = v; 114 } else { 115 httpMethod = v.substring(0, i).trim(); 116 path = v.substring(i).trim(); 117 } 118 } else { 119 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())); 120 if (value.isPresent()) 121 path = value.get(); 122 } 123 124 if (path.isEmpty()) { 125 path = HttpUtils.detectHttpPath(m, Utils.nullIfEmpty3(httpMethod)); 126 } 127 if (httpMethod.isEmpty()) 128 httpMethod = HttpUtils.detectHttpMethod(m, true, defaultMethod); 129 130 path = trimSlashes(path); 131 132 if (! isOneOf(httpMethod, "DELETE", "GET", "POST", "PUT", "OPTIONS", "HEAD", "CONNECT", "TRACE", "PATCH")) 133 throw new RemoteMetadataException(m, 134 "Invalid value specified for @RemoteOp(httpMethod) annotation: '"+httpMethod+"'. Valid values are [DELETE,GET,POST,PUT,OPTIONS,HEAD,CONNECT,TRACE,PATCH]."); 135 136 methodReturn = new RemoteOperationReturn(mi); 137 138 fullPath = path.indexOf("://") != -1 ? path : (parentPath.isEmpty() ? urlEncodePath(path) : (trimSlashes(parentPath) + '/' + urlEncodePath(path))); 139 140 mi.getParams().forEach(x -> { 141 RemoteOperationArg rma = RemoteOperationArg.create(x); 142 if (rma != null) { 143 HttpPartType pt = rma.getPartType(); 144 if (pt == HEADER) 145 headerArgs.add(rma); 146 else if (pt == QUERY) 147 queryArgs.add(rma); 148 else if (pt == FORMDATA) 149 formDataArgs.add(rma); 150 else if (pt == PATH) 151 pathArgs.add(rma); 152 else 153 bodyArg = rma; 154 } 155 RequestBeanMeta rmba = RequestBeanMeta.create(x, AnnotationWorkList.create()); 156 if (rmba != null) { 157 requestArgs.add(new RemoteOperationBeanArg(x.getIndex(), rmba)); 158 } 159 }); 160 } 161 } 162 163 /** 164 * Returns the value of the {@link RemoteOp#method() @RemoteOp(method)} annotation on this Java method. 165 * 166 * @return The value of the annotation, never <jk>null</jk>. 167 */ 168 public String getHttpMethod() { 169 return httpMethod; 170 } 171 172 /** 173 * Returns the absolute URI of the REST interface invoked by this Java method. 174 * 175 * @return The absolute URI of the REST interface, never <jk>null</jk>. 176 */ 177 public String getFullPath() { 178 return fullPath; 179 } 180 181 /** 182 * Performs an action on the {@link Path @Path} annotated arguments on this Java method. 183 * 184 * @param action The action to perform. 185 * @return This object. 186 */ 187 public RemoteOperationMeta forEachPathArg(Consumer<RemoteOperationArg> action) { 188 for (RemoteOperationArg a : pathArgs) 189 action.accept(a); 190 return this; 191 } 192 193 /** 194 * Performs an action on the {@link Query @Query} annotated arguments on this Java method. 195 * 196 * @param action The action to perform. 197 * @return This object. 198 */ 199 public RemoteOperationMeta forEachQueryArg(Consumer<RemoteOperationArg> action) { 200 for (RemoteOperationArg a : queryArgs) 201 action.accept(a); 202 return this; 203 } 204 205 /** 206 * Performs an action on the {@link FormData @FormData} annotated arguments on this Java method. 207 * 208 * @param action The action to perform. 209 * @return This object. 210 */ 211 public RemoteOperationMeta forEachFormDataArg(Consumer<RemoteOperationArg> action) { 212 for (RemoteOperationArg a : formDataArgs) 213 action.accept(a); 214 return this; 215 } 216 217 /** 218 * Performs an action on the {@link Header @Header} annotated arguments on this Java method. 219 * 220 * @param action The action to perform. 221 * @return This object. 222 */ 223 public RemoteOperationMeta forEachHeaderArg(Consumer<RemoteOperationArg> action) { 224 for (RemoteOperationArg a : headerArgs) 225 action.accept(a); 226 return this; 227 } 228 229 /** 230 * Performs an action on the {@link Request @Request} annotated arguments on this Java method. 231 * 232 * @param action The action to perform. 233 * @return This object. 234 */ 235 public RemoteOperationMeta forEachRequestArg(Consumer<RemoteOperationBeanArg> action) { 236 for (RemoteOperationBeanArg a : requestArgs) 237 action.accept(a); 238 return this; 239 } 240 241 /** 242 * Returns the argument annotated with {@link Content @Content}. 243 * 244 * @return A index of the argument with the {@link Content @Content} annotation, or <jk>null</jk> if no argument exists. 245 */ 246 public RemoteOperationArg getContentArg() { 247 return contentArg; 248 } 249 250 /** 251 * Returns whether the method returns the HTTP response body or status code. 252 * 253 * @return Whether the method returns the HTTP response body or status code. 254 */ 255 public RemoteOperationReturn getReturns() { 256 return methodReturn; 257 } 258 259 /** 260 * Performs an action on the exceptions thrown by this method. 261 * 262 * @param action The action to perform. 263 * @return This object. 264 */ 265 public RemoteOperationMeta forEachException(Consumer<Class<?>> action) { 266 for (Class<?> e : exceptions) 267 action.accept(e); 268 return this; 269 } 270}