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.mock2;
014
015import java.util.*;
016
017import org.apache.juneau.json.*;
018import org.apache.juneau.marshall.*;
019import org.apache.juneau.parser.*;
020import org.apache.juneau.rest.annotation.*;
021import org.apache.juneau.rest.client.*;
022import org.apache.juneau.http.remote.*;
023import org.apache.juneau.serializer.*;
024
025/**
026 * Creates a mocked interface against a REST resource class to use for creating test remote resource interfaces.
027 *
028 * <ul class='seealso'>
029 *    <li class='link'>{@doc juneau-rest-mock.MockRemote}
030 * </ul>
031 *
032 * @param <T> The interface class.
033 */
034public class MockRemote<T> {
035
036   private MockRest.Builder mrb;
037   private RestClientBuilder rcb = RestClient.create().json();
038   private final Class<T> intf;
039
040   /**
041    * Constructor.
042    *
043    * @param intf
044    *    The remote interface annotated with {@link Remote @Remote}.
045    * @param impl
046    *    The REST implementation bean or bean class annotated with {@link Rest @Rest}.
047    *    <br>If a class, it must have a no-arg constructor.
048    */
049   protected MockRemote(Class<T> intf, Object impl) {
050      this.intf = intf;
051      mrb = MockRest.create(impl);
052   }
053
054   /**
055    * Create a new builder using the specified remote resource interface and REST implementation bean or bean class.
056    *
057    * <p>
058    * Uses {@link JsonSerializer#DEFAULT} and {@link JsonParser#DEFAULT} for serializing and parsing by default.
059    *
060    *
061    * @param intf
062    *    The remote interface annotated with {@link Remote @Remote}.
063    * @param impl
064    *    The REST implementation bean or bean class annotated with {@link Rest @Rest}.
065    *    <br>If a class, it must have a no-arg constructor.
066    * @return A new builder.
067    */
068   public static <T> MockRemote<T> create(Class<T> intf, Object impl) {
069      return new MockRemote<>(intf, impl);
070   }
071
072   /**
073    * Create a new builder using the specified remote resource interface and REST implementation bean or bean class.
074    *
075    * <p>
076    * Uses the serializer and parser defined on the specified marshall for serializing and parsing by default.
077    *
078    * @param intf
079    *    The remote interface annotated with {@link Remote @Remote}.
080    * @param impl
081    *    The REST implementation bean or bean class annotated with {@link Rest @Rest}.
082    *    <br>If a class, it must have a no-arg constructor.
083    * @param m
084    *    The marshall to use for serializing and parsing the HTTP bodies.
085    * @return A new builder.
086    */
087   public static <T> MockRemote<T> create(Class<T> intf, Object impl, Marshall m) {
088      return new MockRemote<>(intf, impl).marshall(m);
089   }
090
091   /**
092    * Create a new builder using the specified remote resource interface and REST implementation bean or bean class.
093    *
094    * <p>
095    * Uses the serializer and parser defined on the specified marshall for serializing and parsing by default.
096    *
097    * @param intf
098    *    The remote interface annotated with {@link Remote @Remote}.
099    * @param impl
100    *    The REST implementation bean or bean class annotated with {@link Rest @Rest}.
101    *    <br>If a class, it must have a no-arg constructor.
102    * @param s
103    *    The serializer to use for serializing request bodies.
104    *    <br>Can be <jk>null</jk> to force no serializer to be used and no <c>Content-Type</c> header.
105    * @param p
106    *    The parser to use for parsing response bodies.
107    *    <br>Can be <jk>null</jk> to force no parser to be used and no <c>Accept</c> header.
108    * @return A new builder.
109    */
110   public static <T> MockRemote<T> create(Class<T> intf, Object impl, Serializer s, Parser p) {
111      return new MockRemote<>(intf, impl).serializer(s).parser(p);
112   }
113
114   /**
115    * Constructs a remote proxy interface based on the settings of this builder.
116    *
117    * @return A new remote proxy interface.
118    */
119   public T build() {
120      MockRest mr = mrb.build();
121      return rcb.httpClientConnectionManager(new MockHttpClientConnectionManager(mr)).rootUrl("http://localhost").headers(mr.getHeaders()).build().getRemote(intf);
122   }
123
124   /**
125    * Convenience method for getting a remote resource interface.
126    *
127    * <p>
128    * Uses {@link JsonSerializer#DEFAULT} and {@link JsonParser#DEFAULT} for serializing and parsing by default.
129    *
130    * <p>
131    * Equivalent to calling the following:
132    * <p class='bcode w800'>
133    *    MockRemoteResource.<jsf>create</jsf>(intf, impl).build();
134    * </p>
135    *
136    * @param intf
137    *    The remote interface annotated with {@link Remote @Remote}.
138    * @param impl
139    *    The REST implementation bean or bean class annotated with {@link Rest @Rest}.
140    *    <br>If a class, it must have a no-arg constructor.
141    * @return A new proxy interface.
142    */
143   public static <T> T build(Class<T> intf, Object impl) {
144      return create(intf, impl).build();
145   }
146
147   /**
148    * Convenience method for getting a remote resource interface.
149    *
150    * <p>
151    * Uses the serializer and parser defined on the specified marshall for serializing and parsing by default.
152    *
153    * <p>
154    * Equivalent to calling the following:
155    * <p class='bcode w800'>
156    *    MockRemoteResource.<jsf>create</jsf>(intf, impl).marshall(m).build();
157    * </p>
158    *
159    * @param intf
160    *    The remote interface annotated with {@link Remote @Remote}.
161    * @param impl
162    *    The REST implementation bean or bean class annotated with {@link Rest @Rest}.
163    *    <br>If a class, it must have a no-arg constructor.
164    * @param m
165    *    The marshall to use for serializing request bodies and parsing response bodies.
166    *    <br>Can be <jk>null</jk> to force no serializer or parser to be used and no <c>Accept</c> or <c>Content-Type</c> header.
167    * @return A new proxy interface.
168    */
169   public static <T> T build(Class<T> intf, Object impl, Marshall m) {
170      return create(intf, impl).marshall(m).build();
171   }
172
173   /**
174    * Convenience method for getting a remote resource interface.
175    *
176    * <p>
177    * Uses the specified serializer and parser for serializing and parsing by default.
178    *
179    * <p>
180    * Equivalent to calling the following:
181    * <p class='bcode w800'>
182    *    MockRemoteResource.<jsf>create</jsf>(intf, impl).serializer(s).parser(p).build();
183    * </p>
184    *
185    * @param intf
186    *    The remote interface annotated with {@link Remote @Remote}.
187    * @param impl
188    *    The REST implementation bean or bean class annotated with {@link Rest @Rest}.
189    *    <br>If a class, it must have a no-arg constructor.
190    * @param s
191    *    The serializer to use for serializing request bodies.
192    *    <br>Can be <jk>null</jk> to force no serializer to be used and no <c>Content-Type</c> header.
193    * @param p
194    *    The parser to use for parsing response bodies.
195    *    <br>Can be <jk>null</jk> to force no parser to be used and no <c>Accept</c> header.
196    * @return A new proxy interface.
197    */
198   public static <T> T build(Class<T> intf, Object impl, Serializer s, Parser p) {
199      return create(intf, impl).serializer(s).parser(p).build();
200   }
201
202   /**
203    * Enable debug mode.
204    *
205    * @return This object (for method chaining).
206    */
207   public MockRemote<T> debug() {
208      mrb.debug();
209      rcb.debug();
210      return this;
211   }
212
213   /**
214    * Adds a header to every request.
215    *
216    * @param name The header name.
217    * @param value
218    *    The header value.
219    *    <br>Can be <jk>null</jk> (will be skipped).
220    * @return This object (for method chaining).
221    */
222   public MockRemote<T> header(String name, Object value) {
223      mrb.header(name, value);
224      rcb.header(name, value);
225      return this;
226   }
227
228   /**
229    * Adds the specified headers to every request.
230    *
231    * @param value
232    *    The header values.
233    *    <br>Can be <jk>null</jk> (existing values will be cleared).
234    *    <br><jk>null</jk> null map values will be ignored.
235    * @return This object (for method chaining).
236    */
237   public MockRemote<T> headers(Map<String,Object> value) {
238      mrb.headers(value);
239      rcb.headers(value);
240      return this;
241   }
242
243   /**
244    * Adds an <c>Accept</c> header to every request.
245    *
246    * @param value The <c>Accept</c> header value.
247    * @return This object (for method chaining).
248    */
249   public MockRemote<T> accept(String value) {
250      mrb.accept(value);
251      rcb.accept(value);
252      return this;
253   }
254
255   /**
256    * Adds a <c>Content-Type</c> header to every request.
257    *
258    * @param value The <c>Content-Type</c> header value.
259    * @return This object (for method chaining).
260    */
261   public MockRemote<T> contentType(String value) {
262      mrb.contentType(value);
263      rcb.contentType(value);
264      return this;
265   }
266
267   /**
268    * Convenience method for setting <c>Accept</c> and <c>Content-Type</c> headers to <js>"application/json"</js>.
269    *
270    * @return This object (for method chaining).
271    */
272   public MockRemote<T> json() {
273      marshall(Json.DEFAULT);
274      return this;
275   }
276
277   /**
278    * Convenience method for setting <c>Accept</c> and <c>Content-Type</c> headers to <js>"application/json+simple"</js>.
279    *
280    * @return This object (for method chaining).
281    */
282   public MockRemote<T> simpleJson() {
283      marshall(SimpleJson.DEFAULT);
284      return this;
285   }
286
287   /**
288    * Convenience method for setting <c>Accept</c> and <c>Content-Type</c> headers to <js>"text/xml"</js>.
289    *
290    * @return This object (for method chaining).
291    */
292   public MockRemote<T> xml() {
293      marshall(Xml.DEFAULT);
294      return this;
295   }
296
297   /**
298    * Convenience method for setting <c>Accept</c> and <c>Content-Type</c> headers to <js>"text/html"</js>.
299    *
300    * @return This object (for method chaining).
301    */
302   public MockRemote<T> html() {
303      marshall(Html.DEFAULT);
304      return this;
305   }
306
307   /**
308    * Convenience method for setting <c>Accept</c> and <c>Content-Type</c> headers to <js>"text/plain"</js>.
309    *
310    * @return This object (for method chaining).
311    */
312   public MockRemote<T> plainText() {
313      marshall(PlainText.DEFAULT);
314      return this;
315   }
316
317   /**
318    * Convenience method for setting <c>Accept</c> and <c>Content-Type</c> headers to <js>"octal/msgpack"</js>.
319    *
320    * @return This object (for method chaining).
321    */
322   public MockRemote<T> msgpack() {
323      marshall(MsgPack.DEFAULT);
324      return this;
325   }
326
327   /**
328    * Convenience method for setting <c>Accept</c> and <c>Content-Type</c> headers to <js>"text/uon"</js>.
329    *
330    * @return This object (for method chaining).
331    */
332   public MockRemote<T> uon() {
333      marshall(Uon.DEFAULT);
334      return this;
335   }
336
337   /**
338    * Convenience method for setting <c>Accept</c> and <c>Content-Type</c> headers to <js>"application/x-www-form-urlencoded"</js>.
339    *
340    * @return This object (for method chaining).
341    */
342   public MockRemote<T> urlEnc() {
343      marshall(UrlEncoding.DEFAULT);
344      return this;
345   }
346
347   /**
348    * Convenience method for setting <c>Accept</c> and <c>Content-Type</c> headers to <js>"text/openapi"</js>.
349    *
350    * @return This object (for method chaining).
351    */
352   public MockRemote<T> openapi() {
353      marshall(OpenApi.DEFAULT);
354      return this;
355   }
356
357   /**
358    * Associates the specified {@link Marshall} with this client.
359    *
360    * <p>
361    * This is shorthand for calling <c>serializer(x)</c> and <c>parser(x)</c> using the inner
362    * serializer and parser of the marshall object.
363    *
364    * @param value
365    *    The marshall to use for serializing and parsing HTTP bodies.
366    *    <br>Can be <jk>null</jk> (will remote the existing serializer/parser).
367    * @return This object (for method chaining).
368    */
369   public MockRemote<T> marshall(Marshall value) {
370      if (value != null)
371         serializer(value.getSerializer()).parser(value.getParser());
372      else
373         serializer(null).parser(null);
374      return this;
375   }
376
377   /**
378    * Associates the specified {@link Serializer} with this client.
379    *
380    * @param value
381    *    The serializer to use for serializing HTTP bodies.
382    *    <br>Can be <jk>null</jk> (will remote the existing serializer).
383    * @return This object (for method chaining).
384    */
385   public MockRemote<T> serializer(Serializer value) {
386      rcb.serializer(value);
387      contentType(value == null ? null : value.getPrimaryMediaType().toString());
388      return this;
389   }
390
391   /**
392    * Associates the specified {@link Parser} with this client.
393    *
394    * @param value
395    *    The parser to use for parsing HTTP bodies.
396    *    <br>Can be <jk>null</jk> (will remote the existing parser).
397    * @return This object (for method chaining).
398    */
399   public MockRemote<T> parser(Parser value) {
400      rcb.parser(value);
401      accept(value == null ? null : value.getPrimaryMediaType().toString());
402      return this;
403   }
404}