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.classic.remote;
018
019import static org.apache.juneau.commons.utils.ClassUtils.*;
020import static org.apache.juneau.commons.utils.CollectionUtils.*;
021import static org.apache.juneau.commons.utils.Shorts.*;
022import static org.apache.juneau.commons.utils.StringUtils.*;
023import static org.apache.juneau.http.classic.HttpHeaders.*;
024
025import java.lang.reflect.*;
026import java.util.*;
027
028import org.apache.juneau.commons.reflect.*;
029import org.apache.juneau.commons.svl.*;
030import org.apache.juneau.http.classic.header.*;
031import org.apache.juneau.http.remote.*;
032
033/**
034 * Contains the meta-data about a REST proxy class.
035 *
036 * <p>
037 * Captures the information in {@link Remote @Remote} and {@link RemoteOp @RemoteOp} annotations for
038 * caching and reuse.
039 *
040 * <h5 class='section'>See Also:</h5><ul>
041 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestProxies">REST Proxies</a>
042 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestClient">juneau-rest-client-classic</a>
043 * </ul>
044 */
045public class RemoteMeta {
046
047   private static String resolve(String s) {
048      return VarResolver.DEFAULT.resolve(s);
049   }
050
051   private final Map<Method,RemoteOperationMeta> operations;
052
053   private final HeaderList headers;
054
055   // Interface-level members honored for classic/NG parity (B-client-1).
056   private final String baseUrl;
057   private final String accept;
058   private final String contentType;
059   private final List<Map.Entry<String,String>> queryData;
060   private final List<Map.Entry<String,String>> formData;
061   private final String timeout;
062   private final int retries;
063   private final boolean retryNonIdempotent;
064   private final boolean throwOnError;
065   private final boolean allowPrivateUrls;
066
067   /**
068    * Constructor.
069    *
070    * @param c The interface class annotated with a {@link Remote @Remote} annotation (optional).
071    */
072   @SuppressWarnings({
073      "java:S3776" // Cognitive complexity acceptable for remote interface metadata construction dispatch
074   })
075   public RemoteMeta(Class<?> c) {
076      var path = "";
077
078      var ci = ClassInfo.of(c);
079      var remotes = rstream(ci.getAnnotations(Remote.class).toList()).map(AnnotationInfo::inner).toList();
080
081      var versionHeader = "Client-Version";
082      String clientVersion = null;
083      var headers2 = HeaderList.create().resolving();
084
085      var baseUrl2 = "";
086      var accept2 = "";
087      var contentType2 = "";
088      var queryData2 = new ArrayList<Map.Entry<String,String>>();
089      var formData2 = new ArrayList<Map.Entry<String,String>>();
090      var timeout2 = "";
091      var retries2 = 0;
092      var retryNonIdempotent2 = false;
093      var throwOnError2 = false;
094      var allowPrivateUrls2 = false;
095
096      for (var r : remotes) {
097         if (ine(r.path()))
098            path = trimSlashes(resolve(r.path()));
099         else if (ine(r.value()))
100            path = trimSlashes(resolve(r.value()));
101         for (var h : r.headers())
102            headers2.append(stringHeader(resolve(h)));
103         if (ine(r.version()))
104            clientVersion = resolve(r.version());
105         if (ine(r.versionHeader()))
106            versionHeader = resolve(r.versionHeader());
107         if (isNotVoid(r.headerList()) && HeaderList.class.isAssignableFrom(r.headerList())) {
108            try {
109               headers2.append(((HeaderList) r.headerList().getDeclaredConstructor().newInstance()).getAll());
110            } catch (Exception e) {
111               throw rex(e, "Could not instantiate HeaderSupplier class");
112            }
113         }
114         if (ine(r.baseUrl()))
115            baseUrl2 = resolve(r.baseUrl());
116         if (ine(r.accept()))
117            accept2 = resolve(r.accept());
118         if (ine(r.contentType()))
119            contentType2 = resolve(r.contentType());
120         queryData2.addAll(RemoteProxyUtils.parseConstantParts(r.queryData(), '='));
121         formData2.addAll(RemoteProxyUtils.parseConstantParts(r.formData(), '='));
122         if (ine(r.timeout()))
123            timeout2 = r.timeout();
124         if (r.retries() > 0)
125            retries2 = r.retries();
126         retryNonIdempotent2 |= r.retryNonIdempotent();
127         throwOnError2 |= r.throwOnError();
128         allowPrivateUrls2 |= r.allowPrivateUrls();
129         // Genuinely engine-specific: classic and NG RestCallInterceptor SPI types are nominally
130         // incompatible, so the classic engine cannot honor interface-level interceptors.  Warn once.
131         if (r.interceptors().length > 0)
132            RemoteProxyUtils.warnUnsupportedMember(c, "interceptors",
133               "classic and next-generation RestCallInterceptor SPI types are nominally incompatible.");
134      }
135
136      if (nn(clientVersion))
137         headers2.append(stringHeader(versionHeader, clientVersion));
138
139      Map<Method,RemoteOperationMeta> operations2 = map();
140      var path2 = path;
141      ci.getPublicMethods().forEach(x -> operations2.put(x.inner(), new RemoteOperationMeta(path2, x.inner(), "GET")));
142
143      this.operations = u(operations2);
144      this.headers = headers2.unmodifiable();
145      this.baseUrl = baseUrl2;
146      this.accept = accept2;
147      this.contentType = contentType2;
148      this.queryData = u(queryData2);
149      this.formData = u(formData2);
150      this.timeout = timeout2;
151      this.retries = retries2;
152      this.retryNonIdempotent = retryNonIdempotent2;
153      this.throwOnError = throwOnError2;
154      this.allowPrivateUrls = allowPrivateUrls2;
155   }
156
157   /**
158    * Returns the headers to set on all requests.
159    *
160    * @return The headers to set on all requests.
161    */
162   public HeaderList getHeaders() { return headers; }
163
164   /**
165    * Returns the interface-level base/host override from {@link Remote#baseUrl()}.
166    *
167    * @return The base/host override. Never <jk>null</jk>, but may be empty.
168    */
169   public String getBaseUrl() { return baseUrl; }
170
171   /**
172    * Returns the interface-level default {@code Accept} media type from {@link Remote#accept()}.
173    *
174    * @return The accept media type. Never <jk>null</jk>, but may be empty.
175    */
176   public String getAccept() { return accept; }
177
178   /**
179    * Returns the interface-level default {@code Content-Type} media type from {@link Remote#contentType()}.
180    *
181    * @return The content-type media type. Never <jk>null</jk>, but may be empty.
182    */
183   public String getContentType() { return contentType; }
184
185   /**
186    * Returns the interface-level constant query parameters from {@link Remote#queryData()}.
187    *
188    * @return An unmodifiable list of name/value entries. Never <jk>null</jk>, but may be empty.
189    */
190   public List<Map.Entry<String,String>> getQueryData() { return queryData; }
191
192   /**
193    * Returns the interface-level constant form-data parameters from {@link Remote#formData()}.
194    *
195    * @return An unmodifiable list of name/value entries. Never <jk>null</jk>, but may be empty.
196    */
197   public List<Map.Entry<String,String>> getFormData() { return formData; }
198
199   /**
200    * Returns the interface-level default per-call timeout duration string from {@link Remote#timeout()}.
201    *
202    * @return The timeout duration string. Never <jk>null</jk>, but may be empty.
203    */
204   public String getTimeout() { return timeout; }
205
206   /**
207    * Returns the interface-level default maximum retry attempts from {@link Remote#retries()}.
208    *
209    * @return The retry count.
210    */
211   public int getRetries() { return retries; }
212
213   /**
214    * Returns whether the interface opts non-idempotent verbs into automatic retries ({@link Remote#retryNonIdempotent()}).
215    *
216    * @return <jk>true</jk> if non-idempotent retries are opted in at the interface level.
217    */
218   public boolean isRetryNonIdempotent() { return retryNonIdempotent; }
219
220   /**
221    * Returns whether the interface throws a generic exception on an unmatched error response ({@link Remote#throwOnError()}).
222    *
223    * @return <jk>true</jk> if {@code throwOnError} is set at the interface level.
224    */
225   public boolean isThrowOnError() { return throwOnError; }
226
227   /**
228    * Returns whether the interface opts its absolute {@code @Remote}/{@code @Url}/{@code baseUrl()} targets out of
229    * the default deny-private SSRF guardrail ({@link Remote#allowPrivateUrls()}).
230    *
231    * @return <jk>true</jk> if {@code allowPrivateUrls} is set at the interface level.
232    */
233   public boolean isAllowPrivateUrls() { return allowPrivateUrls; }
234
235   /**
236    * Returns the metadata about the specified operation on this resource proxy.
237    *
238    * @param m The method to look up.
239    * @return Metadata about the method or <jk>null</jk> if no metadata was found.
240    */
241   public RemoteOperationMeta getOperationMeta(Method m) {
242      return operations.get(m);
243   }
244}