    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}