001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.rest.mock; 018 019import static org.apache.juneau.commons.utils.CollectionUtils.*; 020import static org.apache.juneau.commons.utils.Utils.*; 021 022import java.io.*; 023import java.time.Instant; 024import java.time.ZoneId; 025import java.time.format.DateTimeFormatter; 026import java.util.*; 027 028import org.apache.juneau.rest.util.*; 029 030import jakarta.servlet.*; 031import jakarta.servlet.http.*; 032 033/** 034 * An implementation of {@link HttpServletResponse} for mocking purposes. 035 * 036 * <h5 class='section'>See Also:</h5><ul> 037 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestMockBasics">juneau-rest-mock Basics</a> 038 * </ul> 039*/ 040public class MockServletResponse implements HttpServletResponse { 041 042 /** 043 * Creates a new servlet response. 044 * 045 * @return A new response. 046 */ 047 public static MockServletResponse create() { 048 return new MockServletResponse(); 049 } 050 051 private String characterEncoding = "UTF-8"; 052 private ByteArrayOutputStream baos = new ByteArrayOutputStream(); 053 private int bufferSize; 054 private Locale locale; 055 private int sc; 056 private String msg; 057 058 private Map<String,String[]> headerMap = map(); 059 060 @Override /* Overridden from HttpServletResponse */ 061 public void addCookie(Cookie cookie) {} 062 063 @Override /* Overridden from HttpServletResponse */ 064 public void addDateHeader(String name, long date) { 065 Instant instant = Instant.ofEpochMilli(date); 066 DateTimeFormatter formatter = DateTimeFormatter.RFC_1123_DATE_TIME 067 .withZone(ZoneId.of("GMT")); 068 headerMap.put(name, a(formatter.format(instant))); 069 } 070 071 @Override /* Overridden from HttpServletResponse */ 072 public void addHeader(String name, String value) { 073 headerMap.put(name, a(value)); 074 } 075 076 @Override /* Overridden from HttpServletResponse */ 077 public void addIntHeader(String name, int value) { 078 headerMap.put(name, a(String.valueOf(value))); 079 } 080 081 @Override /* Overridden from HttpServletResponse */ 082 public boolean containsHeader(String name) { 083 return nn(getHeader(name)); 084 } 085 086 @Override /* Overridden from HttpServletResponse */ 087 public String encodeRedirectURL(String url) { 088 return null; 089 } 090 091 @Override /* Overridden from HttpServletResponse */ 092 public String encodeURL(String url) { 093 return null; 094 } 095 096 @Override /* Overridden from HttpServletResponse */ 097 public void flushBuffer() throws IOException {} 098 099 @Override /* Overridden from HttpServletResponse */ 100 public int getBufferSize() { return bufferSize; } 101 102 @Override /* Overridden from HttpServletResponse */ 103 public String getCharacterEncoding() { return characterEncoding; } 104 105 @Override /* Overridden from HttpServletResponse */ 106 public String getContentType() { return getHeader("Content-Type"); } 107 108 @Override /* Overridden from HttpServletResponse */ 109 public String getHeader(String name) { 110 String[] s = headerMap.get(name); 111 return s == null || s.length == 0 ? null : s[0]; 112 } 113 114 @Override /* Overridden from HttpServletResponse */ 115 public Collection<String> getHeaderNames() { return headerMap.keySet(); } 116 117 @Override /* Overridden from HttpServletResponse */ 118 public Collection<String> getHeaders(String name) { 119 String[] s = headerMap.get(name); 120 return s == null ? Collections.emptyList() : u(l(s)); 121 } 122 123 @Override /* Overridden from HttpServletResponse */ 124 public Locale getLocale() { return locale; } 125 126 /** 127 * Returns the response message. 128 * 129 * @return The response message. 130 */ 131 public String getMessage() { return msg; } 132 133 @Override /* Overridden from HttpServletResponse */ 134 public ServletOutputStream getOutputStream() throws IOException { return new FinishableServletOutputStream(baos); } 135 136 @Override /* Overridden from HttpServletResponse */ 137 public int getStatus() { return sc; } 138 139 @Override /* Overridden from HttpServletResponse */ 140 public PrintWriter getWriter() throws IOException { return new PrintWriter(new OutputStreamWriter(getOutputStream(), characterEncoding)); } 141 142 /** 143 * Fluent setter for {@link #setHeader(String,String)}. 144 * 145 * @param name The header name. 146 * @param value The new header value. 147 * @return This object. 148 */ 149 public MockServletResponse header(String name, String value) { 150 setHeader(name, value); 151 return this; 152 } 153 154 @Override /* Overridden from HttpServletResponse */ 155 public boolean isCommitted() { return false; } 156 157 @Override /* Overridden from HttpServletResponse */ 158 public void reset() {} 159 160 @Override /* Overridden from HttpServletResponse */ 161 public void resetBuffer() {} 162 163 @Override /* Overridden from HttpServletResponse */ 164 public void sendError(int sc) throws IOException { 165 this.sc = sc; 166 } 167 168 @Override /* Overridden from HttpServletResponse */ 169 public void sendError(int sc, String msg) throws IOException { 170 this.sc = sc; 171 this.msg = msg; 172 } 173 174 @Override /* Overridden from HttpServletResponse */ 175 public void sendRedirect(String location) throws IOException { 176 this.sc = 302; 177 headerMap.put("Location", a(location)); 178 } 179 180 @Override /* Overridden from HttpServletResponse */ 181 public void sendRedirect(String location, int sc, boolean clearBuffer) throws IOException { 182 this.sc = sc; 183 headerMap.put("Location", a(location)); 184 } 185 186 @Override /* Overridden from HttpServletResponse */ 187 public void setBufferSize(int size) { this.bufferSize = size; } 188 189 @Override /* Overridden from HttpServletResponse */ 190 public void setCharacterEncoding(String charset) { 191 this.characterEncoding = charset; 192 updateContentTypeHeader(); 193 } 194 195 @Override /* Overridden from HttpServletResponse */ 196 public void setContentLength(int len) { 197 header("Content-Length", String.valueOf(len)); 198 } 199 200 @Override /* Overridden from HttpServletResponse */ 201 public void setContentLengthLong(long len) { 202 header("Content-Length", String.valueOf(len)); 203 } 204 205 @Override /* Overridden from HttpServletResponse */ 206 public void setContentType(String type) { 207 setHeader("Content-Type", type); 208 updateContentTypeHeader(); 209 } 210 211 @Override /* Overridden from HttpServletResponse */ 212 public void setDateHeader(String name, long date) { 213 Instant instant = Instant.ofEpochMilli(date); 214 DateTimeFormatter formatter = DateTimeFormatter.RFC_1123_DATE_TIME 215 .withZone(ZoneId.of("GMT")); 216 headerMap.put(name, a(formatter.format(instant))); 217 } 218 219 @Override /* Overridden from HttpServletResponse */ 220 public void setHeader(String name, String value) { 221 headerMap.put(name, a(value)); 222 } 223 224 @Override /* Overridden from HttpServletResponse */ 225 public void setIntHeader(String name, int value) { 226 headerMap.put(name, a(String.valueOf(value))); 227 } 228 229 @Override /* Overridden from HttpServletResponse */ 230 public void setLocale(Locale loc) { this.locale = loc; } 231 232 @Override /* Overridden from HttpServletResponse */ 233 public void setStatus(int sc) { this.sc = sc; } 234 235 /** 236 * Fluent setter for {@link #setStatus(int)}. 237 * 238 * @param value The new property value. 239 * @return This object. 240 */ 241 public MockServletResponse status(int value) { 242 setStatus(value); 243 return this; 244 } 245 246 private void updateContentTypeHeader() { 247 var contentType = getContentType(); 248 String charset = characterEncoding; 249 if (nn(contentType) && nn(charset)) { 250 if (contentType.indexOf("charset=") != -1) 251 contentType = contentType.replaceAll("\\;\\s*charset=.*", ""); 252 if (! "UTF-8".equalsIgnoreCase(charset)) 253 contentType = contentType + ";charset=" + charset; 254 header("Content-Type", contentType); 255 } 256 } 257 258 byte[] getContent() throws IOException { 259 baos.flush(); 260 return baos.toByteArray(); 261 } 262 263 Map<String,String[]> getHeaders() { return headerMap; } 264}