package io.mangoo.routing;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.apache.commons.io.IOUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.google.common.base.Charsets;
import io.mangoo.enums.ContentType;
import io.mangoo.enums.Required;
import io.mangoo.utils.JsonUtils;
import io.undertow.server.handlers.Cookie;
import io.undertow.util.Headers;
import io.undertow.util.HttpString;
import io.undertow.util.StatusCodes;
/**
*
* @author svenkubiak
*
*/
public final class Response {
private static final Logger LOG = LogManager.getLogger(Response.class);
private final Map<HttpString, String> headers = new HashMap<>();
private final Map<String, Object> content = new HashMap<>();
private final List<Cookie> cookies = new ArrayList<>();
private String redirectTo;
private String contentType = ContentType.TEXT_PLAIN.toString();
private String charset = Charsets.UTF_8.name();
private String body = "";
private String template;
private String binaryFileName;
private byte[] binaryContent;
private boolean endResponse;
private boolean etag;
private boolean binary;
private boolean rendered;
private boolean redirect;
private int statusCode = StatusCodes.OK;
public Response() {
//Empty constructor for Google Guice
}
private Response(int statusCode) {
this.statusCode = statusCode;
}
private Response(String redirectTo) {
Objects.requireNonNull(redirectTo, Required.REDIRECT_TO.toString());
this.redirect = true;
this.rendered = true;
this.redirectTo = redirectTo;
}
public int getStatusCode() {
return this.statusCode;
}
public String getContentType() {
return this.contentType;
}
public String getCharset() {
return this.charset;
}
public String getBody() {
return this.body;
}
public List<Cookie> getCookies() {
return this.cookies;
}
public byte[] getBinaryContent() {
return this.binaryContent.clone();
}
public String getTemplate() {
return this.template;
}
public boolean isETag() {
return this.etag;
}
public String getBinaryFileName() {
return this.binaryFileName;
}
public Map<String, Object> getContent() {
return this.content;
}
public boolean isRedirect() {
return this.redirect;
}
public boolean isBinary() {
return this.binary;
}
public boolean isRendered() {
return this.rendered;
}
public boolean isEndResponse() {
return this.endResponse;
}
public String getRedirectTo() {
return this.redirectTo;
}
public Map<HttpString, String> getHeaders() {
return headers;
}
/**
* Creates a response object with HTTP status code 200
*
* @return A response object {@link io.mangoo.routing.Response}
*/
public static Response withOk() {
return new Response(StatusCodes.OK);
}
/**
* Creates a response object with HTTP status code 201
*
* @return A response object {@link io.mangoo.routing.Response}
*/
public static Response withCreated() {
return new Response(StatusCodes.CREATED);
}
/**
* Creates a response object with HTTP status code 404
*
* @return A response object {@link io.mangoo.routing.Response}
*/
public static Response withNotFound() {
return new Response(StatusCodes.NOT_FOUND);
}
/**
* Creates a response object with HTTP status code 401
*
* @return A response object {@link io.mangoo.routing.Response}
*/
public static Response withForbidden() {
return new Response(StatusCodes.FORBIDDEN);
}
/**
* Creates a response object with HTTP status code 403
*
* @return A response object {@link io.mangoo.routing.Response}
*/
public static Response withUnauthorized() {
return new Response(StatusCodes.UNAUTHORIZED);
}
/**
* Creates a response object with HTTP status code 400
*
* @return A response object {@link io.mangoo.routing.Response}
*/
public static Response withBadRequest() {
return new Response(StatusCodes.BAD_REQUEST);
}
/**
* Creates a response object with HTTP status code 500
*
* @return A response object {@link io.mangoo.routing.Response}
*/
public static Response withInternalServerError() {
return new Response(StatusCodes.INTERNAL_SERVER_ERROR);
}
/**
* Creates a response object with a given HTTP status code
*
* @param statusCode The status code to set
* @return A response object {@link io.mangoo.routing.Response}
*/
public static Response withStatusCode(int statusCode) {
return new Response(statusCode);
}
/**
* Creates a response object with a given url to redirect to
*
* @param redirectTo The URL to redirect to
* @return A response object {@link io.mangoo.routing.Response}
*/
public static Response withRedirect(String redirectTo) {
Objects.requireNonNull(redirectTo, Required.REDIRECT_TO.toString());
return new Response(redirectTo);
}
/**
* Sets a specific template to use for the response
*
* @param template The path to the template (e.g. /mytemplate/template.ftl)
* @return A response object {@link io.mangoo.routing.Response}
*/
public Response andTemplate(String template) {
Objects.requireNonNull(template, Required.TEMPLATE.toString());
this.template = template;
return this;
}
/**
* Sets a specific content type to use for the response. Default is "text/html"
*
* @param contentType The content type to use
* @return A response object {@link io.mangoo.routing.Response}
*/
public Response andContentType(String contentType) {
Objects.requireNonNull(contentType, Required.CONTENT_TYPE.toString());
this.contentType = contentType;
this.headers.put(Headers.CONTENT_TYPE, contentType);
return this;
}
/**
* Sets a specific charset to the response
*
* @param charset The charset to use
* @return A response object {@link io.mangoo.routing.Response}
*/
public Response andCharset(String charset) {
Objects.requireNonNull(charset, Required.CHARSET.toString());
this.charset = charset;
return this;
}
/**
* Adds a value to the template that can be accessed using ${name} in the template
*
* @param name The name of the value
* @param object The actual value
* @return A response object {@link io.mangoo.routing.Response}
*/
public Response andContent(String name, Object object) {
Objects.requireNonNull(name, Required.NAME.toString());
this.content.put(name, object);
return this;
}
/**
* Sets the body of the response. If a body is added, no template rendering will be
* performed. The default content type "text/html" will be used.
*
* @param body The body for the response
* @return A response object {@link io.mangoo.routing.Response}
*/
public Response andBody(String body) {
this.body = body;
this.rendered = true;
this.contentType = ContentType.TEXT_HTML.toString();
return this;
}
/**
* Adds an additional Cookie to the response which is passed to the client
*
* @param cookie The cookie to add
* @return A response object {@link io.mangoo.routing.Response}
*/
public Response andCookie(Cookie cookie) {
Objects.requireNonNull(cookie, Required.COOKIE.toString());
this.cookies.add(cookie);
return this;
}
/**
* Converts a given Object to JSON and passing it to the response. If an object is given, no
* template rendering will be performed and the content type for the response will be set to
* "application/json"
*
* @param jsonObject The object to convert to JSON
* @return A response object {@link io.mangoo.routing.Response}
*/
public Response andJsonBody(Object jsonObject) {
Objects.requireNonNull(jsonObject, Required.JSON_OBJECT.toString());
this.contentType = ContentType.APPLICATION_JSON.toString();
this.body = JsonUtils.toJson(jsonObject);
this.rendered = true;
return this;
}
/**
* Sends a binary file to the client skipping rendering
*
* @param file The file to send
* @return A response object {@link io.mangoo.routing.Response}
*/
public Response andBinaryFile(File file) {
Objects.requireNonNull(file, Required.FILE.toString());
try (FileInputStream fileInputStream = new FileInputStream(file)){
this.binaryFileName = file.getName();
this.binaryContent = IOUtils.toByteArray(fileInputStream);
this.binary = true;
this.rendered = true;
} catch (final IOException e) {
LOG.error("Failed to handle binary file", e);
}
return this;
}
/**
* Sends binary content to the client skipping rendering
*
* @param content The content to to send
* @return A response object {@link io.mangoo.routing.Response}
*/
public Response andBinaryContent(byte [] content) {
Objects.requireNonNull(content, Required.CONTENT.toString());
this.binaryContent = content.clone();
this.binary = true;
this.rendered = true;
return this;
}
/**
* Sets the body of the response. If a body is added, no template rendering will be
* performed. The content type "text/plain" will be used.
*
* @param text The text for the response
*
* @return A response object {@link io.mangoo.routing.Response}
*/
public Response andTextBody(String text) {
this.contentType = ContentType.TEXT_PLAIN.toString();
this.body = text;
this.rendered = true;
return this;
}
/**
* Disables template rendering, sending an empty body in the response
*
* @return A response object {@link io.mangoo.routing.Response}
*/
public Response andEmptyBody() {
this.contentType = ContentType.TEXT_PLAIN.toString();
this.rendered = true;
return this;
}
/**
* Adds an additional header to the request response. If an header
* key already exists, it will we overwritten with the latest value.
*
* @param key The header constant from Headers class (e.g. Headers.CONTENT_TYPE)
* @param value The header value
*
* @return A response object {@link io.mangoo.routing.Response}
*/
public Response andHeader(HttpString key, String value) {
Objects.requireNonNull(key, Required.KEY.toString());
this.headers.put(key, value);
return this;
}
/**
* Adds an additional content map to the content rendered in the template.
* Already existing values with the same key are overwritten.
*
* @param content The content map to add
* @return A response object {@link io.mangoo.routing.Response}
*/
public Response andContent(Map<String, Object> content) {
Objects.requireNonNull(content, Required.CONTENT.toString());
this.content.putAll(content);
return this;
}
/**
* Adds an additional header map to the response.
* Already existing values with the same key are overwritten.
*
* @param headers The headers map to add
* @return A response object {@link io.mangoo.routing.Response}
*/
public Response andHeaders(Map<HttpString, String> headers) {
Objects.requireNonNull(headers, Required.HEADERS.toString());
this.headers.putAll(headers);
return this;
}
/**
* Adds an ETag header to the response by hashing (MD5) the response body.
*
* Be aware that for every request the hash has to be generated. This will
* most likely increase CPU usage.
*
* See <a href="https://en.wikipedia.org/wiki/HTTP_ETag">https://en.wikipedia.org/wiki/HTTP_ETag</a>
*
* @return A response object {@link io.mangoo.routing.Response}
*/
public Response andEtag() {
this.etag = true;
return this;
}
/**
* Tells a filter that the response ends and that the request handler
* should not execute further filters by sending the current response
* to the client. This is only used within a filter.
*
* @return A response object {@link io.mangoo.routing.Response}
*/
public Response end() {
this.endResponse = true;
return this;
}
}