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