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.mock;
31  
32  import com.jcabi.aspects.Immutable;
33  import com.jcabi.aspects.Loggable;
34  import com.jcabi.http.ImmutableHeader;
35  import com.jcabi.http.RequestBody;
36  import com.jcabi.immutable.Array;
37  import com.jcabi.log.Logger;
38  import java.net.HttpURLConnection;
39  import java.nio.charset.Charset;
40  import java.util.LinkedList;
41  import java.util.List;
42  import java.util.Map;
43  import java.util.concurrent.ConcurrentHashMap;
44  import java.util.concurrent.ConcurrentMap;
45  import lombok.EqualsAndHashCode;
46  
47  /**
48   * Mock response.
49   *
50   * @since 0.10
51   */
52  @Immutable
53  @SuppressWarnings("PMD.TooManyMethods")
54  public interface MkAnswer {
55  
56      /**
57       * HTTP response status.
58       * @return The status code
59       */
60      int status();
61  
62      /**
63       * HTTP response headers.
64       * @return The headers
65       */
66      Map<String, List<String>> headers();
67  
68      /**
69       * HTTP response body.
70       * @return The body, as a UTF-8 string
71       */
72      String body();
73  
74      /**
75       * HTTP response body as bytes.
76       * @return The body, as byte array
77       */
78      byte[] bodyBytes();
79  
80      /**
81       * Simple implementation.
82       *
83       * @since 1.0
84       */
85      @Immutable
86      @EqualsAndHashCode(of = {"code", "hdrs", "content"})
87      @Loggable(Loggable.DEBUG)
88      final class Simple implements MkAnswer {
89          /**
90           * The Charset to use.
91           */
92          private static final Charset CHARSET = Charset.forName("UTF-8");
93  
94          /**
95           * Encapsulated response.
96           */
97          private final transient int code;
98  
99          /**
100          * Headers.
101          */
102         private final transient Array<Map.Entry<String, String>> hdrs;
103 
104         /**
105          * Content received.
106          */
107         @Immutable.Array
108         private final transient byte[] content;
109 
110         /**
111          * Public ctor.
112          * @param body Body of HTTP response
113          */
114         public Simple(final String body) {
115             this(HttpURLConnection.HTTP_OK, body);
116         }
117 
118         /**
119          * Public ctor (with empty HTTP body).
120          * @param status HTTP status
121          * @since 1.9
122          */
123         public Simple(final int status) {
124             this(status, "");
125         }
126 
127         /**
128          * Public ctor.
129          * @param status HTTP status
130          * @param body Body of HTTP response
131          */
132         public Simple(final int status, final String body) {
133             this(
134                 status, new Array<Map.Entry<String, String>>(),
135                 body.getBytes(MkAnswer.Simple.CHARSET)
136             );
137         }
138 
139         /**
140          * Public ctor.
141          * @param status HTTP status
142          * @param headers HTTP headers
143          * @param body Body of HTTP response
144          */
145         public Simple(final int status,
146             final Iterable<Map.Entry<String, String>> headers,
147             final byte[] body) {
148             this.code = status;
149             this.hdrs = new Array<>(headers);
150             this.content = body.clone();
151         }
152 
153         @Override
154         public int status() {
155             return this.code;
156         }
157 
158         @Override
159         @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
160         public Map<String, List<String>> headers() {
161             final ConcurrentMap<String, List<String>> map =
162                 new ConcurrentHashMap<>(0);
163             for (final Map.Entry<String, String> header : this.hdrs) {
164                 map.putIfAbsent(header.getKey(), new LinkedList<String>());
165                 map.get(header.getKey()).add(header.getValue());
166             }
167             return map;
168         }
169 
170         @Override
171         public String body() {
172             return new String(this.content, MkAnswer.Simple.CHARSET);
173         }
174 
175         @Override
176         public byte[] bodyBytes() {
177             return this.content.clone();
178         }
179 
180         @Override
181         public String toString() {
182             final StringBuilder text = new StringBuilder(0)
183                 .append(this.code).append('\n');
184             for (final Map.Entry<String, String> header : this.hdrs) {
185                 text.append(
186                     Logger.format(
187                         "%s: %s\n",
188                         header.getKey(),
189                         header.getValue()
190                     )
191                 );
192             }
193             return text.append('\n')
194                 .append(new RequestBody.Printable(this.content))
195                 .toString();
196         }
197 
198         /**
199          * Make a copy of this answer, with an extra header.
200          * @param name Name of the header
201          * @param value ImmutableHeader value
202          * @return New answer
203          */
204         public MkAnswer.Simple withHeader(final String name,
205             final String value) {
206             return new MkAnswer.Simple(
207                 this.code,
208                 this.hdrs.with(new ImmutableHeader(name, value)),
209                 this.content
210             );
211         }
212 
213         /**
214          * Make a copy of this answer, with another status code.
215          * @param status Status code
216          * @return New answer
217          */
218         public MkAnswer.Simple withStatus(final int status) {
219             return new MkAnswer.Simple(
220                 status,
221                 this.hdrs,
222                 this.content
223             );
224         }
225 
226         /**
227          * Make a copy of this answer, with another body.
228          * @param body Body
229          * @return New answer
230          */
231         public MkAnswer.Simple withBody(final String body) {
232             return new MkAnswer.Simple(
233                 this.code,
234                 this.hdrs,
235                 body.getBytes(MkAnswer.Simple.CHARSET)
236             );
237         }
238 
239         /**
240          * Make a copy of this answer, with another body.
241          * @param body Body
242          * @return New answer
243          */
244         public MkAnswer.Simple withBody(final byte[] body) {
245             return new MkAnswer.Simple(this.code, this.hdrs, body);
246         }
247     }
248 
249 }