juzu.Response Maven / Gradle / Ivy
/*
* Copyright 2013 eXo Platform SAS
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package juzu;
import juzu.impl.common.Formatting;
import juzu.io.ChunkBuffer;
import juzu.io.Stream;
import juzu.io.Streamable;
import juzu.io.UndeclaredIOException;
import juzu.io.Chunk;
import juzu.request.Dispatch;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.util.AbstractMap;
/**
* A response object signalling to the framework the action to take after an interaction. This object is usually
* returned after the invocation of a controller method and instructs the action to perform.
*
* Action response
*
* Redirection response
* A Response.Process.Action.Redirect
response instructs Juzu to make a redirection to a valid
* URL after the interaction, this kind of response is created using the factory method {@link Response#redirect(String)}:
*
* return Response.redirect("http://www.exoplatform.org");
*
*
*
* Proceed to render phase
* A Response.View
response instructs Juzu to proceed to the render phase of a valid view
* controller, this kind of response can be created using an {@link juzu.request.ActionContext}, however the best
* way is to use a controller companion class that carries method factories for creating render responses.
*
* Type safe {@link juzu.Response.View} factory method are generated for each view or resource controller
* methods. The signature of an render factory is obtained by using the same signature of the controller method.
*
*
* public class MyController {
*
* @Action
* public {@link juzu.Response.View} myAction() {
* return MyController_.myView("hello");
* }
*
* @View
* public void myView(String param) {
* }
* }
*
*
* Mime response
*
* Mime response are used by the {@link juzu.request.Phase#VIEW} and the {@link juzu.request.Phase#RESOURCE} phases.
* Both contains a content to be streamed to the client but still they have some noticeable differences.
*
* The {@link juzu.Response.Content} class is the base response class which will work well for the two phases.
* However the {@link juzu.request.Phase#VIEW} can specify an optional title and the {@link juzu.request.Phase#RESOURCE}
* can specify an optional status code for the user agent response.
*
* Responses are created using the {@link Response} factory methods such as
*
*
* - {@link Response#ok} creates an ok response
* - {@link Response#notFound} creates a not found response
*
*
* Response can also created from {@link juzu.template.Template} directly:
*
*
* public class MyController {
*
* @Inject @Path("index.gtmpl") {@link juzu.template.Template} index;
*
* @View
* public {@link juzu.Response.Content} myView() {
* return index.ok();
* }
*
* @Inject @Path("error.gtmpl") {@link juzu.template.Template} error;
*
* @Resource
* public {@link juzu.Response.Content} myView() {
* return error.notFound();
* }
* }
*
*
* The {@link juzu.template.Template.Builder} can also create responses:
*
*
* public class MyController {
*
* @Inject @Path("index.gtmpl") index index;
*
* @View
* public {@link juzu.Response.Content} myView() {
* return index.with().label("hello").ok();
* }
* }
*
*
* @author Julien Viet
*/
public abstract class Response {
/** . */
protected final PropertyMap properties;
protected Response() {
this.properties = new PropertyMap();
}
protected Response(PropertyMap properties) {
this.properties = properties;
}
/**
* Set a property, if the value is null, the property is removed.
*
* @param propertyType the property type
* @param propertyValue the property value
* @throws NullPointerException if the property type is null
*/
public Response with(PropertyType propertyType, T propertyValue) throws NullPointerException {
if (propertyType == null) {
throw new NullPointerException("No null property type allowed");
}
properties.addValue(propertyType, propertyValue);
return this;
}
/**
* Removes a property.
*
* @param propertyType the property type
* @throws NullPointerException if the property type is null
*/
public Response without(PropertyType propertyType) throws NullPointerException {
return with(propertyType, null);
}
/**
* Set a boolean property to true.
*
* @param propertyType the property type
* @throws NullPointerException if the property type is null
*/
public Response with(PropertyType propertyType) throws NullPointerException {
return with(propertyType, true);
}
/**
* Set a boolean property to false.
*
* @param propertyType the property type
* @throws NullPointerException if the property type is null
*/
public Response withNo(PropertyType propertyType) throws NullPointerException {
return with(propertyType, false);
}
public final PropertyMap getProperties() {
return properties;
}
public Response withHeader(String name, String... value) {
return with(PropertyType.HEADER, new AbstractMap.SimpleEntry(name, value));
}
/**
* A response instructing to execute a render phase of a controller method after the current interaction.
*/
public static abstract class View extends Response implements Dispatch {
@Override
public View with(PropertyType propertyType, T propertyValue) throws NullPointerException {
return (View)super.with(propertyType, propertyValue);
}
@Override
public View withHeader(String name, String... value) {
return (View)super.withHeader(name, value);
}
@Override
public View without(PropertyType propertyType) throws NullPointerException {
return (View)super.without(propertyType);
}
@Override
public View with(PropertyType propertyType) throws NullPointerException {
return (View)super.with(propertyType);
}
@Override
public View withNo(PropertyType propertyType) throws NullPointerException {
return (View)super.withNo(propertyType);
}
public abstract boolean equals(Object obj);
}
/**
* A response instructing to execute an HTTP redirection after the current interaction.
*/
public static class Redirect extends Response {
/** . */
private final String location;
public Redirect(String location) {
this.location = location;
}
public String getLocation() {
return location;
}
@Override
public Redirect with(PropertyType propertyType, T propertyValue) throws NullPointerException {
return (Redirect)super.with(propertyType, propertyValue);
}
@Override
public Redirect withHeader(String name, String... value) {
return (Redirect)super.withHeader(name, value);
}
@Override
public Redirect without(PropertyType propertyType) throws NullPointerException {
return (Redirect)super.without(propertyType);
}
@Override
public Redirect with(PropertyType propertyType) throws NullPointerException {
return (Redirect)super.with(propertyType);
}
@Override
public Redirect withNo(PropertyType propertyType) throws NullPointerException {
return (Redirect)super.withNo(propertyType);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof Redirect) {
Redirect that = (Redirect)obj;
return location.equals(that.location);
}
return false;
}
@Override
public String toString() {
return "Response.Redirect[location" + location + "]";
}
}
public static class Status extends Response {
/** . */
private int code;
public Status(int code) {
this.code = code;
}
public Status(int code, PropertyMap properties) {
super(properties);
//
this.code = code;
}
public final int getCode() {
return code;
}
public Body body(Streamable s) {
return new Body(code, properties, s);
}
public Body body(ChunkBuffer s) {
return new Body(code, properties, s);
}
public Body body(CharSequence s) {
return body(new ChunkBuffer().append(Chunk.create(s)).close());
}
public Body body(byte[] s) {
return body(new ChunkBuffer().append(Chunk.create(s)).close());
}
public Body body(java.io.InputStream s) {
return body(new ChunkBuffer().append(Chunk.create(s)).close());
}
public Content content(Streamable s) {
return new Content(code, properties, s);
}
public Content content(CharSequence s) {
return content(new ChunkBuffer().append(Chunk.create(s)).close());
}
public Content content(byte[] s) {
return content(new ChunkBuffer().append(Chunk.create(s)).close());
}
public Content content(java.io.InputStream s) {
return content(new ChunkBuffer().append(Chunk.create(s)).close());
}
@Override
public Status withHeader(String name, String... value) {
return (Status)super.withHeader(name, value);
}
@Override
public Status withNo(PropertyType propertyType) throws NullPointerException {
return (Status)super.withNo(propertyType);
}
@Override
public Status with(PropertyType propertyType) throws NullPointerException {
return (Status)super.with(propertyType);
}
@Override
public Status without(PropertyType propertyType) throws NullPointerException {
return (Status)super.without(propertyType);
}
@Override
public Status with(PropertyType propertyType, T propertyValue) throws NullPointerException {
return (Status)super.with(propertyType, propertyValue);
}
public Streamable streamable() {
return new Streamable() {
public void send(Stream stream) throws IllegalStateException {
// Send properties
for (PropertyType> propertyType : properties) {
Iterable> values = properties.getValues(propertyType);
if (values != null) {
for (Object o : values) {
stream.provide(new Chunk.Property(o, propertyType));
}
}
}
// Send real stream
if (Status.this instanceof Response.Body) {
((Response.Body)Status.this).getData().send(stream);
} else {
stream.close(null);
}
}
};
}
}
public static class Body extends Status {
/** . */
private Streamable data;
protected Body(int status, PropertyMap properties) {
super(status, properties);
//
this.data = null;
}
protected Body(int status, Streamable data) {
super(status);
//
this.data = data;
}
protected Body(int status, PropertyMap properties, Streamable data) {
super(status, properties);
//
this.data = data;
}
public Streamable getData() {
return data;
}
public String getMimeType() {
return properties.getValue(PropertyType.MIME_TYPE);
}
public Charset getCharset() {
return properties.getValue(PropertyType.ENCODING);
}
public Body withCharset(Charset charset) {
return with(PropertyType.ENCODING, charset);
}
public Body withMimeType(String mimeType) {
return with(PropertyType.MIME_TYPE, mimeType);
}
@Override
public Body withHeader(String name, String... value) {
return (Body)super.withHeader(name, value);
}
@Override
public Body with(PropertyType propertyType, T propertyValue) throws NullPointerException {
return (Body)super.with(propertyType, propertyValue);
}
@Override
public Body without(PropertyType propertyType) throws NullPointerException {
return (Body)super.without(propertyType);
}
@Override
public Body with(PropertyType propertyType) throws NullPointerException {
return (Body)super.with(propertyType);
}
@Override
public Body withNo(PropertyType propertyType) throws NullPointerException {
return (Body)super.withNo(propertyType);
}
}
public static class Content extends Body {
public Content(int status, PropertyMap properties, Streamable streamable) {
super(status, properties, streamable);
}
public Content(int status, Streamable streamable) {
super(status, streamable);
}
public Content(PropertyMap properties, Streamable streamable) {
super(200, properties, streamable);
}
public Content(Streamable streamable) {
super(200, streamable);
}
@Override
public Content with(PropertyType propertyType, T propertyValue) throws NullPointerException {
return (Content)super.with(propertyType, propertyValue);
}
@Override
public Content without(PropertyType propertyType) throws NullPointerException {
return (Content)super.without(propertyType);
}
@Override
public Content with(PropertyType propertyType) throws NullPointerException {
return (Content)super.with(propertyType);
}
@Override
public Content withNo(PropertyType propertyType) throws NullPointerException {
return (Content)super.withNo(propertyType);
}
@Override
public Content withMimeType(String mimeType) {
return (Content)super.withMimeType(mimeType);
}
@Override
public Content withCharset(Charset charset) {
return (Content)super.withCharset(charset);
}
@Override
public Content withHeader(String name, String... value) {
return (Content)super.withHeader(name, value);
}
public String getTitle() {
return properties.getValue(PropertyType.TITLE);
}
public Content withTitle(String title) {
return with(PropertyType.TITLE, title);
}
public Content withAssets(String... assets) throws NullPointerException {
if (assets == null) {
throw new NullPointerException("No null asset accepted");
}
for (String asset : assets) {
with(PropertyType.ASSET, asset);
}
return this;
}
public Content withMetaTag(String name, String value) {
with(PropertyType.META_TAG, new AbstractMap.SimpleEntry(name, value));
return this;
}
public Content withMetaHttpEquiv(String name, String value) {
with(PropertyType.META_HTTP_EQUIV, new AbstractMap.SimpleEntry(name, value));
return this;
}
/**
* Parse the header into an {@link Element} and set it on the response as an header tag. This method
* expects well formed XML, the parsed Element will be translated into markup according to the
* response content type when the response will be written to the document.
*
* @param header the header string to parse
* @return this object
* @throws ParserConfigurationException any ParserConfigurationException
* @throws SAXException any SAXException
*/
public Content withHeaderTag(String header) throws ParserConfigurationException, SAXException {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dbf.newDocumentBuilder();
try {
Document doc = builder.parse(new InputSource(new StringReader(header)));
Element elt = doc.getDocumentElement();
return withHeaderTag(elt);
}
catch (IOException e) {
// Let's save user from IOException at least
throw new UndeclaredIOException(e);
}
}
/**
* Set the provided element on the response as an HTML header.
*
* @param header the header
* @return this object
*/
public Content withHeaderTag(Element header) {
return with(PropertyType.HEADER_TAG, header);
}
@Override
public String toString() {
return "Response.Content[]";
}
}
public static class Error extends Response {
/** . */
private final Throwable cause;
/** . */
private final String message;
public Error(Throwable cause) {
this(null, cause);
}
public Error(String message) {
this(message, null);
}
private Error(String message, Throwable cause) {
this.cause = cause;
this.message = message;
}
public Throwable getCause() {
return cause;
}
public String getMessage() {
return message;
}
/**
* @return the HTML formatted message, the default implementation returns the raw message
*/
public String getHtmlMessage() {
return message;
}
protected int getStatus() {
return 500;
}
public Status asStatus(boolean verbose) {
Response.Status response = Response.status(getStatus());
if (verbose) {
StringBuilder buffer = new StringBuilder();
Formatting.renderStyleSheet(buffer);
buffer.append("");
buffer.append("Oups something went wrong
");
// Use getCause as it can be overriden with subclasses
Throwable c = getCause();
if (c != null) {
Formatting.renderThrowable(null, buffer, c);
} else {
// Use getMessage as it can be overriden with subclasses
String m = getHtmlMessage();
buffer.append(m);
}
buffer.append("");
response = response.content(buffer).withMimeType("text/html");
}
return response;
}
@Override
public String toString() {
return "Response.Error[" + (cause != null ? cause.getMessage() : "") + "]";
}
/**
* A specific subclass for forbidden access.
*/
public static class Forbidden extends Error {
public Forbidden(Throwable cause) {
super(cause);
}
public Forbidden(String message) {
super(message);
}
public Forbidden(String message, Throwable cause) {
super(message, cause);
}
@Override
protected int getStatus() {
return 403;
}
}
}
public static Response.Redirect redirect(String location) {
return new Response.Redirect(location);
}
public static Status status(int code) {
return new Status(code);
}
public static Status ok() {
return status(200);
}
public static Status notFound() {
return status(404);
}
public static Content ok(java.io.InputStream content) {
return content(200, content);
}
public static Content ok(byte[] content) {
return content(200, content);
}
public static Content ok(Readable content) {
return content(200, content);
}
public static Content ok(CharSequence content) {
return content(200, content);
}
public static Content notFound(byte[] content) {
return content(404, content);
}
public static Content notFound(java.io.InputStream content) {
return content(404, content);
}
public static Content notFound(Readable content) {
return content(404, content);
}
public static Content notFound(CharSequence content) {
return content(404, content);
}
public static Content content(int code, byte[] content) {
return content(code, new ChunkBuffer().append(Chunk.create(content)).close());
}
public static Content content(int code, java.io.InputStream content) {
return content(code, new ChunkBuffer().append(Chunk.create(content)).close());
}
public static Content content(int code, Readable content) {
return content(code, new ChunkBuffer().append(Chunk.create(content)).close());
}
public static Content content(int code, CharSequence content) {
return content(code, new ChunkBuffer().append(Chunk.create(content)).close());
}
public static Content content(int code, Streamable content) {
return new Content(code, content);
}
public static Error error(Throwable t) {
return new Error(t);
}
public static Error error(String msg) {
return new Error(msg);
}
}