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 static org.apache.juneau.internal.StringUtils.*;
016
017import java.io.*;
018import java.text.*;
019import java.util.*;
020import java.util.regex.*;
021
022import javax.servlet.*;
023import javax.servlet.http.*;
024
025import org.apache.juneau.internal.*;
026import org.apache.juneau.rest.util.*;
027
028/**
029 * An implementation of {@link HttpServletResponse} for mocking purposes.
030 *
031 * <ul class='seealso'>
032 *    <li class='link'>{@doc juneau-rest-mock.MockRest}
033 * </ul>
034*/
035public class MockServletResponse implements HttpServletResponse, MockHttpResponse {
036
037   private String characterEncoding = "UTF-8";
038   private ByteArrayOutputStream baos = new ByteArrayOutputStream();
039   private long contentLength = 0;
040   private int bufferSize = 0;
041   private Locale locale;
042   private int sc;
043   private String msg;
044   private Map<String,String[]> headerMap = new LinkedHashMap<>();
045
046   /**
047    * Creates a new servlet response.
048    *
049    * @return A new response.
050    */
051   public static MockServletResponse create() {
052      return new MockServletResponse();
053   }
054
055   /**
056    * Returns the content length.
057    *
058    * @return The content length.
059    */
060   public long getContentLength() {
061      return contentLength;
062   }
063
064   /**
065    * Returns the response message.
066    *
067    * @return The response message.
068    */
069   @Override /* MockHttpResponse */
070   public String getMessage() {
071      return msg;
072   }
073
074   @Override /* HttpServletResponse */
075   public String getCharacterEncoding() {
076      return characterEncoding ;
077   }
078
079   @Override /* HttpServletResponse */
080   public String getContentType() {
081      return getHeader("Content-Type");
082   }
083
084   @Override /* HttpServletResponse */
085   public ServletOutputStream getOutputStream() throws IOException {
086      return new FinishableServletOutputStream(baos);
087   }
088
089   @Override /* HttpServletResponse */
090   public PrintWriter getWriter() throws IOException {
091      return new PrintWriter(new OutputStreamWriter(getOutputStream(), characterEncoding));
092   }
093
094   @Override /* HttpServletResponse */
095   public void setCharacterEncoding(String charset) {
096      this.characterEncoding = charset;
097   }
098
099   /**
100    * Fluent setter for {@link #setCharacterEncoding(String)}.
101    *
102    * @param value The new property value.
103    * @return This object (for method chaining).
104    */
105   public MockServletResponse characterEncoding(String value) {
106      setCharacterEncoding(value);
107      return this;
108   }
109
110   @Override /* HttpServletResponse */
111   public void setContentLength(int len) {
112      this.contentLength = len;
113   }
114
115   /**
116    * Fluent setter for {@link #setContentLength(int)}.
117    *
118    * @param value The new property value.
119    * @return This object (for method chaining).
120    */
121   public MockServletResponse contentLength(int value) {
122      setContentLength(value);
123      return this;
124   }
125
126   @Override /* HttpServletResponse */
127   public void setContentLengthLong(long len) {
128      this.contentLength = len;
129   }
130
131   @Override /* HttpServletResponse */
132   public void setContentType(String type) {
133      setHeader("Content-Type", type);
134   }
135
136   /**
137    * Fluent setter for {@link #setContentType(String)}.
138    *
139    * @param value The new property value.
140    * @return This object (for method chaining).
141    */
142   public MockServletResponse contentType(String value) {
143      setContentType(value);
144      return this;
145   }
146
147   @Override /* HttpServletResponse */
148   public void setBufferSize(int size) {
149      this.bufferSize = size;
150   }
151
152   /**
153    * Fluent setter for {@link #bufferSize(int)}.
154    *
155    * @param value The new property value.
156    * @return This object (for method chaining).
157    */
158   public MockServletResponse bufferSize(int value) {
159      setBufferSize(value);
160      return this;
161   }
162
163   @Override /* HttpServletResponse */
164   public int getBufferSize() {
165      return bufferSize;
166   }
167
168   @Override /* HttpServletResponse */
169   public void flushBuffer() throws IOException {
170   }
171
172   @Override /* HttpServletResponse */
173   public void resetBuffer() {
174   }
175
176   @Override /* HttpServletResponse */
177   public boolean isCommitted() {
178      return false;
179   }
180
181   @Override /* HttpServletResponse */
182   public void reset() {
183   }
184
185   @Override /* HttpServletResponse */
186   public void setLocale(Locale loc) {
187      this.locale = loc;
188   }
189
190   /**
191    * Fluent setter for {@link #setLocale(Locale)}.
192    *
193    * @param value The new property value.
194    * @return This object (for method chaining).
195    */
196   public MockServletResponse locale(Locale value) {
197      setLocale(value);
198      return this;
199   }
200
201   @Override /* HttpServletResponse */
202   public Locale getLocale() {
203      return locale;
204   }
205
206   @Override /* HttpServletResponse */
207   public void addCookie(Cookie cookie) {
208   }
209
210   @Override /* HttpServletResponse */
211   public boolean containsHeader(String name) {
212      return getHeader(name) != null;
213   }
214
215   @Override /* HttpServletResponse */
216   public String encodeURL(String url) {
217      return null;
218   }
219
220   @Override /* HttpServletResponse */
221   public String encodeRedirectURL(String url) {
222      return null;
223   }
224
225   @Override /* HttpServletResponse */
226   public String encodeUrl(String url) {
227      return null;
228   }
229
230   @Override /* HttpServletResponse */
231   public String encodeRedirectUrl(String url) {
232      return null;
233   }
234
235   @Override /* HttpServletResponse */
236   public void sendError(int sc, String msg) throws IOException {
237      this.sc = sc;
238      this.msg = msg;
239   }
240
241   @Override /* HttpServletResponse */
242   public void sendError(int sc) throws IOException {
243      this.sc = sc;
244   }
245
246   @Override /* HttpServletResponse */
247   public void sendRedirect(String location) throws IOException {
248      this.sc = 302;
249      headerMap.put("Location", new String[] {location});
250   }
251
252   @Override /* HttpServletResponse */
253   public void setDateHeader(String name, long date) {
254      headerMap.put(name, new String[] {DateUtils.formatDate(new Date(date), DateUtils.PATTERN_RFC1123)});
255   }
256
257   @Override /* HttpServletResponse */
258   public void addDateHeader(String name, long date) {
259      headerMap.put(name, new String[] {DateUtils.formatDate(new Date(date), DateUtils.PATTERN_RFC1123)});
260   }
261
262   @Override /* HttpServletResponse */
263   public void setHeader(String name, String value) {
264      headerMap.put(name, new String[] {value});
265   }
266
267   @Override /* HttpServletResponse */
268   public void addHeader(String name, String value) {
269      headerMap.put(name, new String[] {value});
270   }
271
272   /**
273    * Fluent setter for {@link #setHeader(String,String)}.
274    *
275    * @param name The header name.
276    * @param value The new header value.
277    * @return This object (for method chaining).
278    */
279   public MockServletResponse header(String name, String value) {
280      setHeader(name, value);
281      return this;
282   }
283
284   @Override /* HttpServletResponse */
285   public void setIntHeader(String name, int value) {
286      headerMap.put(name, new String[] {String.valueOf(value)});
287   }
288
289   @Override /* HttpServletResponse */
290   public void addIntHeader(String name, int value) {
291      headerMap.put(name, new String[] {String.valueOf(value)});
292   }
293
294   @Override /* HttpServletResponse */
295   public void setStatus(int sc) {
296      this.sc = sc;
297   }
298
299   /**
300    * Fluent setter for {@link #setStatus(int)}.
301    *
302    * @param value The new property value.
303    * @return This object (for method chaining).
304    */
305   public MockServletResponse status(int value) {
306      setStatus(value);
307      return this;
308   }
309
310   @Override /* HttpServletResponse */
311   public void setStatus(int sc, String sm) {
312      this.sc = sc;
313      this.msg = sm;
314   }
315
316   @Override /* HttpServletResponse */
317   public int getStatus() {
318      return sc;
319   }
320
321   @Override /* HttpServletResponse */
322   public String getHeader(String name) {
323      String[] s = headerMap.get(name);
324      return s == null || s.length == 0 ? null : s[0];
325   }
326
327   @Override /* HttpServletResponse */
328   public Collection<String> getHeaders(String name) {
329      String[] s = headerMap.get(name);
330      return s == null ? Collections.emptyList() : Arrays.asList(s);
331   }
332
333   @Override /* HttpServletResponse */
334   public Collection<String> getHeaderNames() {
335      return headerMap.keySet();
336   }
337
338   /**
339    * Returns the body of the request as a string.
340    *
341    * @return The body of the request as a string.
342    */
343   public String getBodyAsString() {
344      try {
345         return baos.toString("UTF-8");
346      } catch (UnsupportedEncodingException e) {
347         throw new RuntimeException(e);
348      }
349   }
350
351   /**
352    * Throws an {@link AssertionError} if the response status does not match the expected status.
353    *
354    * @param status The expected status.
355    * @return This object (for method chaining).
356    * @throws AssertionError Thrown if status does not match.
357    */
358   public MockServletResponse assertStatus(int status) throws AssertionError {
359      if (getStatus() != status)
360         throw new MockAssertionError("Response did not have the expected status.\n\tExpected=[{0}]\n\tActual=[{1}]", status, getStatus());
361      return this;
362   }
363
364   /**
365    * Throws an {@link AssertionError} if the response body does not contain the expected text.
366    *
367    * @param text The expected text of the body.
368    * @return This object (for method chaining).
369    * @throws AssertionError Thrown if the body does not contain the expected text.
370    */
371   public MockServletResponse assertBody(String text) throws AssertionError {
372      if (! StringUtils.isEquals(text, getBodyAsString()))
373         throw new MockAssertionError("Response did not have the expected text.\n\tExpected=[{0}]\n\tActual=[{1}]", text, getBodyAsString());
374      return this;
375   }
376
377   /**
378    * Throws an {@link AssertionError} if the response body does not contain all of the expected substrings.
379    *
380    * @param substrings The expected substrings.
381    * @return This object (for method chaining).
382    * @throws AssertionError Thrown if the body does not contain one or more of the expected substrings.
383    */
384   public MockServletResponse assertBodyContains(String...substrings) throws AssertionError {
385      String text = getBodyAsString();
386      for (String substring : substrings)
387         if (! contains(text, substring))
388            throw new MockAssertionError("Response did not have the expected substring.\n\tExpected=[{0}]\n\tBody=[{1}]", substring, text);
389      return this;
390   }
391
392   /**
393    * Throws an {@link AssertionError} if the response body does not match the specified pattern.
394    *
395    * <p>
396    * A pattern is a simple string containing <js>"*"</js> to represent zero or more arbitrary characters.
397    *
398    * @param pattern The pattern to match against.
399    * @return This object (for method chaining).
400    * @throws AssertionError Thrown if the body does not match the specified pattern.
401    */
402   public MockServletResponse assertBodyMatches(String pattern) throws AssertionError {
403      String text = getBodyAsString();
404      if (! getMatchPattern(pattern).matcher(text).matches())
405         throw new MockAssertionError("Response did not match expected pattern.\n\tPattern=[{0}]\n\tBody=[{1}]", pattern, text);
406      return this;
407   }
408
409   /**
410    * Throws an {@link AssertionError} if the response body does not match the specified regular expression.
411    *
412    * <p>
413    * A pattern is a simple string containing <js>"*"</js> to represent zero or more arbitrary characters.
414    *
415    * @param regExp The regular expression to match against.
416    * @return This object (for method chaining).
417    * @throws AssertionError Thrown if the body does not match the specified regular expression.
418    */
419   public MockServletResponse assertBodyMatchesRE(String regExp) throws AssertionError {
420      String text = getBodyAsString();
421      if (! Pattern.compile(regExp).matcher(text).matches())
422         throw new MockAssertionError("Response did not match expected regular expression.\n\tRegExp=[{0}]\n\tBody=[{1}]", regExp, text);
423      return this;
424   }
425
426   /**
427    * Throws an {@link AssertionError} if the response does not contain the expected character encoding.
428    *
429    * @param value The expected character encoding.
430    * @return This object (for method chaining).
431    * @throws AssertionError Thrown if the response does not contain the expected character encoding.
432    */
433   public MockServletResponse assertCharset(String value) {
434      if (! StringUtils.isEquals(value, getCharacterEncoding()))
435         throw new MockAssertionError("Response did not have the expected character encoding.\n\tExpected=[{0}]\n\tActual=[{1}]", value, getBodyAsString());
436      return this;
437   }
438
439   /**
440    * Throws an {@link AssertionError} if the response does not contain the expected header value.
441    *
442    * @param name The header name.
443    * @param value The expected header value.
444    * @return This object (for method chaining).
445    * @throws AssertionError Thrown if the response does not contain the expected header value.
446    */
447   public MockServletResponse assertHeader(String name, String value) {
448      if (! StringUtils.isEquals(value, getHeader(name)))
449         throw new MockAssertionError("Response did not have the expected value for header {0}.\n\tExpected=[{1}]\n\tActual=[{2}]", name, value, getHeader(name));
450      return this;
451   }
452
453   /**
454    * Throws an {@link AssertionError} if the response header does not contain all of the expected substrings.
455    *
456    * @param name The header name.
457    * @param substrings The expected substrings.
458    * @return This object (for method chaining).
459    * @throws AssertionError Thrown if the header does not contain one or more of the expected substrings.
460    */
461   public MockServletResponse assertHeaderContains(String name, String...substrings) {
462      String text = getHeader(name);
463      for (String substring : substrings)
464         if (! contains(text, substring))
465            throw new MockAssertionError("Response did not have the expected substring in header {0}.\n\tExpected=[{1}]\n\tHeader=[{2}]", name, substring, text);
466      return this;
467   }
468
469   /**
470    * Returns the body of the request.
471    *
472    * @return The body of the request.
473    */
474   @Override /* MockHttpResponse */
475   public byte[] getBody() {
476      return baos.toByteArray();
477   }
478
479   @Override /* MockHttpResponse */
480   public Map<String,String[]> getHeaders() {
481      return headerMap;
482   }
483
484   private static class MockAssertionError extends AssertionError {
485      private static final long serialVersionUID = 1L;
486
487      MockAssertionError(String msg, Object...args) {
488         super(MessageFormat.format(msg, args));
489         System.err.println(getMessage());  // NOT DEBUG
490      }
491   }
492}