View Javadoc
1   /*
2    * Copyright (c) 2011-2022, jcabi.com
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met: 1) Redistributions of source code must retain the above
8    * copyright notice, this list of conditions and the following
9    * disclaimer. 2) Redistributions in binary form must reproduce the above
10   * copyright notice, this list of conditions and the following
11   * disclaimer in the documentation and/or other materials provided
12   * with the distribution. 3) Neither the name of the jcabi.com nor
13   * the names of its contributors may be used to endorse or promote
14   * products derived from this software without specific prior written
15   * permission.
16   *
17   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
19   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
20   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21   * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
22   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
28   * OF THE POSSIBILITY OF SUCH DAMAGE.
29   */
30  package com.jcabi.http.request;
31  
32  import com.jcabi.aspects.Immutable;
33  import com.jcabi.aspects.Loggable;
34  import com.jcabi.http.ImmutableHeader;
35  import com.jcabi.http.Request;
36  import com.jcabi.http.RequestBody;
37  import com.jcabi.http.RequestURI;
38  import com.jcabi.http.Response;
39  import com.jcabi.http.Wire;
40  import com.jcabi.immutable.Array;
41  import java.io.IOException;
42  import java.io.InputStream;
43  import java.net.HttpURLConnection;
44  import java.nio.charset.Charset;
45  import java.util.Collection;
46  import java.util.Collections;
47  import java.util.Map;
48  import lombok.EqualsAndHashCode;
49  
50  /**
51   * Implementation of {@link Request} that always returns the same
52   * response, specified in the constructor.
53   *
54   * <p>The class is immutable and thread-safe.
55   *
56   * @since 0.9
57   * // @checkstyle ClassDataAbstractionCoupling (500 lines)
58   */
59  @Immutable
60  @EqualsAndHashCode(of = { "base", "code", "phrase", "hdrs", "content" })
61  @Loggable(Loggable.DEBUG)
62  @SuppressWarnings("PMD.TooManyMethods")
63  public final class FakeRequest implements Request {
64  
65      /**
66       * An empty immutable {@code byte} array.
67       */
68      private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
69  
70      /**
71       * The Charset to use.
72       */
73      private static final Charset CHARSET = Charset.forName("UTF-8");
74  
75      /**
76       * Base request.
77       * @checkstyle ParameterNumber (15 lines)
78       */
79      private final transient Request base;
80  
81      /**
82       * Status code.
83       */
84      private final transient int code;
85  
86      /**
87       * Reason phrase.
88       */
89      private final transient String phrase;
90  
91      /**
92       * Headers.
93       */
94      private final transient Array<Map.Entry<String, String>> hdrs;
95  
96      /**
97       * Content received.
98       */
99      @Immutable.Array
100     private final transient byte[] content;
101 
102     /**
103      * Public ctor.
104      */
105     public FakeRequest() {
106         this(
107             HttpURLConnection.HTTP_OK,
108             "OK",
109             Collections.<Map.Entry<String, String>>emptyList(),
110             FakeRequest.EMPTY_BYTE_ARRAY
111         );
112         //@checkstyle ParameterNumber (10 lines)
113     }
114 
115     /**
116      * Public ctor.
117      * @param status HTTP status code to return
118      * @param reason HTTP reason
119      * @param headers HTTP headers
120      * @param body HTTP body
121      */
122     public FakeRequest(final int status, final String reason,
123         final Collection<Map.Entry<String, String>> headers,
124         final byte[] body) {
125         this.code = status;
126         this.phrase = reason;
127         this.hdrs = new Array<>(headers);
128         this.content = body.clone();
129         this.base = new BaseRequest(
130             new Wire() {
131                 @Override
132                 // @checkstyle ParameterNumber (6 lines)
133                 public Response send(final Request req, final String home,
134                     final String method,
135                     final Collection<Map.Entry<String, String>> headers,
136                     final InputStream text,
137                     final int connect,
138                     final int read) {
139                     return new DefaultResponse(
140                         req,
141                         FakeRequest.this.code,
142                         FakeRequest.this.phrase,
143                         FakeRequest.this.hdrs,
144                         FakeRequest.this.content
145                     );
146                 }
147             },
148             "http://localhost:12345/see-FakeRequest-class"
149         );
150     }
151 
152     @Override
153     public String toString() {
154         return this.base.toString();
155     }
156 
157     @Override
158     public RequestURI uri() {
159         return this.base.uri();
160     }
161 
162     @Override
163     public Request header(final String name, final Object value) {
164         return this.base.header(name, value);
165     }
166 
167     @Override
168     public Request reset(final String name) {
169         return this.base.reset(name);
170     }
171 
172     @Override
173     public RequestBody body() {
174         return this.base.body();
175     }
176 
177     @Override
178     public RequestBody multipartBody() {
179         return this.base.multipartBody();
180     }
181 
182     @Override
183     public Request method(final String method) {
184         return this.base.method(method);
185     }
186 
187     @Override
188     public Request timeout(final int connect, final int read) {
189         return this.base.timeout(connect, read);
190     }
191 
192     @Override
193     public Response fetch() throws IOException {
194         return this.base.fetch();
195     }
196 
197     @Override
198     public Response fetch(final InputStream stream) throws IOException {
199         if (this.content.length > 0) {
200             throw new IllegalStateException(
201                 "Request Body is not empty, use fetch() instead"
202             );
203         }
204         return this.base.fetch(stream);
205     }
206 
207     @Override
208     public <T extends Wire> Request through(final Class<T> type,
209         final Object... args) {
210         return this.base.through(type, args);
211     }
212 
213     @Override
214     public Request through(final Wire wire) {
215         return this.base.through(wire);
216     }
217 
218     /**
219      * Make a similar request, with the provided status code.
220      * @param status The code
221      * @return New request
222      */
223     public FakeRequest withStatus(final int status) {
224         return new FakeRequest(
225             status,
226             this.phrase,
227             this.hdrs,
228             this.content
229         );
230     }
231 
232     /**
233      * Make a similar request, with the provided reason line.
234      * @param reason Reason line
235      * @return New request
236      */
237     public FakeRequest withReason(final String reason) {
238         return new FakeRequest(
239             this.code,
240             reason,
241             this.hdrs,
242             this.content
243         );
244     }
245 
246     /**
247      * Make a similar request, with the provided HTTP header.
248      * @param name Name of the header
249      * @param value Value of it
250      * @return New request
251      */
252     public FakeRequest withHeader(final String name, final String value) {
253         return new FakeRequest(
254             this.code,
255             this.phrase,
256             this.hdrs.with(new ImmutableHeader(name, value)),
257             this.content
258         );
259     }
260 
261     /**
262      * Make a similar request, with the provided body.
263      * @param text Body
264      * @return New request
265      */
266     public FakeRequest withBody(final String text) {
267         return this.withBody(text.getBytes(FakeRequest.CHARSET));
268     }
269 
270     /**
271      * Make a similar request, with the provided body.
272      * @param body Body
273      * @return New request
274      */
275     public FakeRequest withBody(final byte[] body) {
276         return new FakeRequest(
277             this.code,
278             this.phrase,
279             this.hdrs,
280             body
281         );
282     }
283 
284 }