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}