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}