001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.test.web.servlet.result; 018 019import java.util.Collections; 020import java.util.Enumeration; 021import java.util.Map; 022import java.util.stream.Collectors; 023 024import javax.servlet.http.Cookie; 025import javax.servlet.http.HttpServletRequest; 026import javax.servlet.http.HttpSession; 027 028import org.springframework.core.style.ToStringCreator; 029import org.springframework.http.HttpHeaders; 030import org.springframework.lang.Nullable; 031import org.springframework.mock.web.MockHttpServletRequest; 032import org.springframework.mock.web.MockHttpServletResponse; 033import org.springframework.test.web.servlet.MvcResult; 034import org.springframework.test.web.servlet.ResultHandler; 035import org.springframework.util.LinkedMultiValueMap; 036import org.springframework.util.MultiValueMap; 037import org.springframework.util.ObjectUtils; 038import org.springframework.validation.BindingResult; 039import org.springframework.validation.Errors; 040import org.springframework.web.method.HandlerMethod; 041import org.springframework.web.servlet.FlashMap; 042import org.springframework.web.servlet.HandlerInterceptor; 043import org.springframework.web.servlet.ModelAndView; 044import org.springframework.web.servlet.support.RequestContextUtils; 045 046/** 047 * Result handler that prints {@link MvcResult} details to a given output 048 * stream — for example: {@code System.out}, {@code System.err}, a 049 * custom {@code java.io.PrintWriter}, etc. 050 * 051 * <p>An instance of this class is typically accessed via one of the 052 * {@link MockMvcResultHandlers#print print} or {@link MockMvcResultHandlers#log log} 053 * methods in {@link MockMvcResultHandlers}. 054 * 055 * @author Rossen Stoyanchev 056 * @author Sam Brannen 057 * @since 3.2 058 */ 059public class PrintingResultHandler implements ResultHandler { 060 061 private static final String MISSING_CHARACTER_ENCODING = "<no character encoding set>"; 062 063 private final ResultValuePrinter printer; 064 065 066 /** 067 * Protected constructor. 068 * @param printer a {@link ResultValuePrinter} to do the actual writing 069 */ 070 protected PrintingResultHandler(ResultValuePrinter printer) { 071 this.printer = printer; 072 } 073 074 /** 075 * Return the result value printer. 076 * @return the printer 077 */ 078 protected ResultValuePrinter getPrinter() { 079 return this.printer; 080 } 081 082 /** 083 * Print {@link MvcResult} details. 084 */ 085 @Override 086 public final void handle(MvcResult result) throws Exception { 087 this.printer.printHeading("MockHttpServletRequest"); 088 printRequest(result.getRequest()); 089 090 this.printer.printHeading("Handler"); 091 printHandler(result.getHandler(), result.getInterceptors()); 092 093 this.printer.printHeading("Async"); 094 printAsyncResult(result); 095 096 this.printer.printHeading("Resolved Exception"); 097 printResolvedException(result.getResolvedException()); 098 099 this.printer.printHeading("ModelAndView"); 100 printModelAndView(result.getModelAndView()); 101 102 this.printer.printHeading("FlashMap"); 103 printFlashMap(RequestContextUtils.getOutputFlashMap(result.getRequest())); 104 105 this.printer.printHeading("MockHttpServletResponse"); 106 printResponse(result.getResponse()); 107 } 108 109 /** 110 * Print the request. 111 */ 112 protected void printRequest(MockHttpServletRequest request) throws Exception { 113 String body = (request.getCharacterEncoding() != null ? 114 request.getContentAsString() : MISSING_CHARACTER_ENCODING); 115 116 this.printer.printValue("HTTP Method", request.getMethod()); 117 this.printer.printValue("Request URI", request.getRequestURI()); 118 this.printer.printValue("Parameters", getParamsMultiValueMap(request)); 119 this.printer.printValue("Headers", getRequestHeaders(request)); 120 this.printer.printValue("Body", body); 121 this.printer.printValue("Session Attrs", getSessionAttributes(request)); 122 } 123 124 protected final HttpHeaders getRequestHeaders(MockHttpServletRequest request) { 125 HttpHeaders headers = new HttpHeaders(); 126 Enumeration<?> names = request.getHeaderNames(); 127 while (names.hasMoreElements()) { 128 String name = (String) names.nextElement(); 129 Enumeration<String> values = request.getHeaders(name); 130 while (values.hasMoreElements()) { 131 headers.add(name, values.nextElement()); 132 } 133 } 134 return headers; 135 } 136 137 protected final MultiValueMap<String, String> getParamsMultiValueMap(MockHttpServletRequest request) { 138 Map<String, String[]> params = request.getParameterMap(); 139 MultiValueMap<String, String> multiValueMap = new LinkedMultiValueMap<>(); 140 params.forEach((name, values) -> { 141 if (params.get(name) != null) { 142 for (String value : values) { 143 multiValueMap.add(name, value); 144 } 145 } 146 }); 147 return multiValueMap; 148 } 149 150 protected final Map<String, Object> getSessionAttributes(MockHttpServletRequest request) { 151 HttpSession session = request.getSession(false); 152 if (session != null) { 153 Enumeration<String> attrNames = session.getAttributeNames(); 154 if (attrNames != null) { 155 return Collections.list(attrNames).stream(). 156 collect(Collectors.toMap(n -> n, session::getAttribute)); 157 } 158 } 159 return Collections.emptyMap(); 160 } 161 162 protected void printAsyncResult(MvcResult result) throws Exception { 163 HttpServletRequest request = result.getRequest(); 164 this.printer.printValue("Async started", request.isAsyncStarted()); 165 Object asyncResult = null; 166 try { 167 asyncResult = result.getAsyncResult(0); 168 } 169 catch (IllegalStateException ex) { 170 // Not set 171 } 172 this.printer.printValue("Async result", asyncResult); 173 } 174 175 /** 176 * Print the handler. 177 */ 178 protected void printHandler(@Nullable Object handler, @Nullable HandlerInterceptor[] interceptors) 179 throws Exception { 180 181 if (handler == null) { 182 this.printer.printValue("Type", null); 183 } 184 else { 185 if (handler instanceof HandlerMethod) { 186 HandlerMethod handlerMethod = (HandlerMethod) handler; 187 this.printer.printValue("Type", handlerMethod.getBeanType().getName()); 188 this.printer.printValue("Method", handlerMethod); 189 } 190 else { 191 this.printer.printValue("Type", handler.getClass().getName()); 192 } 193 } 194 } 195 196 /** 197 * Print exceptions resolved through a HandlerExceptionResolver. 198 */ 199 protected void printResolvedException(@Nullable Exception resolvedException) throws Exception { 200 if (resolvedException == null) { 201 this.printer.printValue("Type", null); 202 } 203 else { 204 this.printer.printValue("Type", resolvedException.getClass().getName()); 205 } 206 } 207 208 /** 209 * Print the ModelAndView. 210 */ 211 protected void printModelAndView(@Nullable ModelAndView mav) throws Exception { 212 this.printer.printValue("View name", (mav != null) ? mav.getViewName() : null); 213 this.printer.printValue("View", (mav != null) ? mav.getView() : null); 214 if (mav == null || mav.getModel().size() == 0) { 215 this.printer.printValue("Model", null); 216 } 217 else { 218 for (String name : mav.getModel().keySet()) { 219 if (!name.startsWith(BindingResult.MODEL_KEY_PREFIX)) { 220 Object value = mav.getModel().get(name); 221 this.printer.printValue("Attribute", name); 222 this.printer.printValue("value", value); 223 Errors errors = (Errors) mav.getModel().get(BindingResult.MODEL_KEY_PREFIX + name); 224 if (errors != null) { 225 this.printer.printValue("errors", errors.getAllErrors()); 226 } 227 } 228 } 229 } 230 } 231 232 /** 233 * Print "output" flash attributes. 234 */ 235 protected void printFlashMap(FlashMap flashMap) throws Exception { 236 if (ObjectUtils.isEmpty(flashMap)) { 237 this.printer.printValue("Attributes", null); 238 } 239 else { 240 flashMap.forEach((name, value) -> { 241 this.printer.printValue("Attribute", name); 242 this.printer.printValue("value", value); 243 }); 244 } 245 } 246 247 /** 248 * Print the response. 249 */ 250 protected void printResponse(MockHttpServletResponse response) throws Exception { 251 String body = (response.getCharacterEncoding() != null ? 252 response.getContentAsString() : MISSING_CHARACTER_ENCODING); 253 254 this.printer.printValue("Status", response.getStatus()); 255 this.printer.printValue("Error message", response.getErrorMessage()); 256 this.printer.printValue("Headers", getResponseHeaders(response)); 257 this.printer.printValue("Content type", response.getContentType()); 258 this.printer.printValue("Body", body); 259 this.printer.printValue("Forwarded URL", response.getForwardedUrl()); 260 this.printer.printValue("Redirected URL", response.getRedirectedUrl()); 261 printCookies(response.getCookies()); 262 } 263 264 /** 265 * Print the supplied cookies in a human-readable form, assuming the 266 * {@link Cookie} implementation does not provide its own {@code toString()}. 267 * @since 4.2 268 */ 269 private void printCookies(Cookie[] cookies) { 270 String[] cookieStrings = new String[cookies.length]; 271 for (int i = 0; i < cookies.length; i++) { 272 Cookie cookie = cookies[i]; 273 cookieStrings[i] = new ToStringCreator(cookie) 274 .append("name", cookie.getName()) 275 .append("value", cookie.getValue()) 276 .append("comment", cookie.getComment()) 277 .append("domain", cookie.getDomain()) 278 .append("maxAge", cookie.getMaxAge()) 279 .append("path", cookie.getPath()) 280 .append("secure", cookie.getSecure()) 281 .append("version", cookie.getVersion()) 282 .append("httpOnly", cookie.isHttpOnly()) 283 .toString(); 284 } 285 this.printer.printValue("Cookies", cookieStrings); 286 } 287 288 protected final HttpHeaders getResponseHeaders(MockHttpServletResponse response) { 289 HttpHeaders headers = new HttpHeaders(); 290 for (String name : response.getHeaderNames()) { 291 headers.put(name, response.getHeaders(name)); 292 } 293 return headers; 294 } 295 296 297 /** 298 * A contract for how to actually write result information. 299 */ 300 protected interface ResultValuePrinter { 301 302 void printHeading(String heading); 303 304 void printValue(String label, @Nullable Object value); 305 } 306 307}