001// Copyright 2021 The Apache Software Foundation 002// 003// Licensed under the Apache License, Version 2.0 (the "License"); 004// you may not use this file except in compliance with the License. 005// 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 010// distributed under the License is distributed on an "AS IS" BASIS, 011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012// See the License for the specific language governing permissions and 013// limitations under the License. 014 015package org.apache.tapestry5.services; 016 017import java.util.Collections; 018import java.util.HashMap; 019import java.util.Map; 020import java.util.Objects; 021 022import javax.servlet.http.HttpServletResponse; 023 024import org.apache.tapestry5.StreamResponse; 025import org.apache.tapestry5.http.Link; 026 027/** 028 * <p> 029 * An event handler method may return an instance of this class to send an specific HTTP status 030 * code to the client. It also supports providing a string to be used as the response body 031 * and extra HTTP headers to be set. This class also provides some utility 032 * static methods for creating instances for specific HTTP statuses and a fluent API for setting 033 * additional information on them. 034 * </p> 035 * 036 * <p> 037 * For returning binary content and/or adding a response header more than once and/or 038 * adding a response header without overwriting existing ones, implementing a {@link StreamResponse} 039 * is the most probable better choice. 040 * </p> 041 * 042 * @since 5.8.0 043 */ 044public final class HttpStatus 045{ 046 private static final String CONTENT_LOCATION_HTTP_HEADER = "Content-Location"; 047 048 private static final String LOCATION_HTTP_HEADER = "Location"; 049 050 private final int statusCode; 051 052 private String responseBody; 053 054 private String contentType; 055 056 private Map<String, String> extraHttpHeaders; 057 058 /** 059 * Creates an instance with status code <code>200 OK</code>. 060 */ 061 public static HttpStatus ok() 062 { 063 return new HttpStatus(HttpServletResponse.SC_OK); 064 } 065 066 /** 067 * Creates an instance with status code <code>201 Created</code>. 068 */ 069 public static HttpStatus created() 070 { 071 return new HttpStatus(HttpServletResponse.SC_CREATED); 072 } 073 074 /** 075 * Creates an instance with status code <code>202 Accepted</code>. 076 */ 077 public static HttpStatus accepted() 078 { 079 return new HttpStatus(HttpServletResponse.SC_ACCEPTED); 080 } 081 082 /** 083 * Creates an instance with status code <code>404 Not Found</code>. 084 */ 085 public static HttpStatus notFound() 086 { 087 return new HttpStatus(HttpServletResponse.SC_NOT_FOUND); 088 } 089 090 /** 091 * Creates an instance with status code <code>403 Forbidden</code>. 092 */ 093 public static HttpStatus forbidden() 094 { 095 return new HttpStatus(HttpServletResponse.SC_FORBIDDEN); 096 } 097 098 /** 099 * Creates an instance with status code <code>400 Bad Request</code>. 100 */ 101 public static HttpStatus badRequest() 102 { 103 return new HttpStatus(HttpServletResponse.SC_BAD_REQUEST); 104 } 105 106 /** 107 * Creates an instance with status code <code>401 Unauthorized</code>. 108 */ 109 public static HttpStatus unauthorized() 110 { 111 return new HttpStatus(HttpServletResponse.SC_UNAUTHORIZED); 112 } 113 114 /** 115 * Creates an instance with status code <code>303 See Other</code>. 116 * @param location the value of the <code>Location</code> header. 117 */ 118 public static HttpStatus seeOther(String location) 119 { 120 return new HttpStatus(HttpServletResponse.SC_SEE_OTHER).withLocation(location); 121 } 122 123 /** 124 * Creates an instance with status code <code>303 See Also</code>. 125 * @param location the value of the <code>Location</code> header. 126 */ 127 public static HttpStatus seeOther(Link location) 128 { 129 return new HttpStatus(HttpServletResponse.SC_SEE_OTHER).withLocation(location); 130 } 131 132 /** 133 * Creates an instance with status code <code>301 Moved Permanently</code>. 134 * @param location the value of the <code>Location</code> header. 135 */ 136 public static HttpStatus movedPermanently(String location) 137 { 138 return new HttpStatus(HttpServletResponse.SC_MOVED_PERMANENTLY).withLocation(location); 139 } 140 141 /** 142 * Creates an instance with status code <code>301 Moved Permanently</code>. 143 * @param link the value of the <code>Location</code> header. 144 */ 145 public static HttpStatus movedPermanently(Link link) 146 { 147 return movedPermanently(link.toRedirectURI()); 148 } 149 150 /** 151 * Creates an instance with status code <code>302 Found</code>. 152 * @param location the value of the <code>Location</code> header. 153 */ 154 public static HttpStatus temporaryRedirect(String location) 155 { 156 return new HttpStatus(HttpServletResponse.SC_FOUND).withLocation(location); 157 } 158 159 /** 160 * Creates an instance with status code <code>302 Found</code>. 161 * @param location the value of the <code>Location</code> header. 162 */ 163 public static HttpStatus temporaryRedirect(Link location) 164 { 165 return temporaryRedirect(location.toRedirectURI()); 166 } 167 168 /** 169 * Creates an object with a given status code and no response body. 170 */ 171 public HttpStatus(int statusCode) 172 { 173 this(statusCode, null, null); 174 } 175 176 /** 177 * Creates an object with a given status code, response body and <code>text/plain</code> MIME content type. 178 */ 179 public HttpStatus(int statusCode, String responseBody) 180 { 181 this(statusCode, responseBody, "text/plain"); 182 } 183 184 /** 185 * Creates an object with a given status code, response body and MIME content type. 186 */ 187 public HttpStatus(int statusCode, String responseBody, String contentType) 188 { 189 this.statusCode = statusCode; 190 this.responseBody = responseBody; 191 this.contentType = contentType; 192 } 193 194 /** 195 * Sets a redirect by using the <code>Location</code> HTTP header. 196 */ 197 public HttpStatus withLocation(Link location) 198 { 199 return withLocation(location.toRedirectURI()); 200 } 201 202 /** 203 * Sets a redirect by using the <code>Location</code> HTTP header. 204 */ 205 public HttpStatus withLocation(String location) 206 { 207 return withHttpHeader(LOCATION_HTTP_HEADER, location); 208 } 209 210 /** 211 * Sets the <code>Content-Location</code> HTTP header. 212 */ 213 public HttpStatus withContentLocation(String location) 214 { 215 return withHttpHeader(CONTENT_LOCATION_HTTP_HEADER, location); 216 } 217 218 /** 219 * Sets the <code>Content-Location</code> HTTP header. 220 */ 221 public HttpStatus withContentLocation(Link link) 222 { 223 return withHttpHeader(CONTENT_LOCATION_HTTP_HEADER, link.toRedirectURI()); 224 } 225 226 /** 227 * Sets an HTTP header. If an existing value for this header already exists, 228 * it gets overwritten. If you need to set multiple headers or add them without 229 * overwriting existing ones, you need to implement {@link StreamResponse} instead. 230 */ 231 public HttpStatus withHttpHeader(String name, String value) 232 { 233 Objects.requireNonNull(name, "Parameter name cannot be null"); 234 Objects.requireNonNull(value, "Parameter value cannot be null"); 235 if (extraHttpHeaders == null) 236 { 237 extraHttpHeaders = new HashMap<>(3); 238 } 239 extraHttpHeaders.put(name, value); 240 return this; 241 } 242 243 /** 244 * Returns the status code. 245 */ 246 public int getStatusCode() 247 { 248 return statusCode; 249 } 250 251 /** 252 * Returns the response body. 253 */ 254 public String getResponseBody() 255 { 256 return responseBody; 257 } 258 259 /** 260 * Returns the MIME content type of the response body. 261 */ 262 public String getContentType() 263 { 264 return contentType; 265 } 266 267 /** 268 * Returns the extra HTTP headers. 269 */ 270 @SuppressWarnings("unchecked") 271 public Map<String, String> getExtraHttpHeaders() { 272 return extraHttpHeaders != null ? extraHttpHeaders : Collections.EMPTY_MAP; 273 } 274 275}