/* * Copyright (C) 2014 Civilian Framework. * * Licensed under the Civilian License (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.civilian-framework.org/license.txt * * 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 org.civilian; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.io.PrintWriter; import java.util.Locale; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; import org.civilian.application.AppConfig; import org.civilian.content.ContentSerializer; import org.civilian.content.ContentType; import org.civilian.content.JaxbXmlSerializer; import org.civilian.provider.ApplicationProvider; import org.civilian.provider.ContextProvider; import org.civilian.provider.LocaleServiceProvider; import org.civilian.provider.RequestProvider; import org.civilian.provider.ResponseProvider; import org.civilian.resource.Url; import org.civilian.response.ResponseHeaders; import org.civilian.response.ResponseStreamInterceptor; import org.civilian.response.ResponseWriterInterceptor; import org.civilian.response.UriEncoder; import org.civilian.response.std.ErrorResponse; import org.civilian.template.Template; import org.civilian.template.TemplateWriter; import org.civilian.text.LocaleService; import org.civilian.type.fn.LocaleSerializer; import org.civilian.util.Check; /** * Response represents a response to a request. * It consists of a {@link Status}, {@link ResponseHeaders headers} and content, produced * either by a {@link #getContentWriter() Writer} or a {@link #getContentStream() OutputStream}.<br> * In a Servlet environment Response is functionally equivalent to a HttpServletResponse. */ public interface Response extends RequestProvider, ResponseProvider, ApplicationProvider, ContextProvider, LocaleServiceProvider { /** * Defines constants for the response status. Their numeric value equals the correspondent HTTP status code. * All status codes are also defined a second time with prefix SC followed by their numerical value. */ public interface Status { public static final int CONTINUE = HttpServletResponse.SC_CONTINUE; public static final int SWITCHING_PROTOCOLS = HttpServletResponse.SC_SWITCHING_PROTOCOLS; public static final int OK = HttpServletResponse.SC_OK; public static final int CREATED = HttpServletResponse.SC_CREATED; public static final int ACCEPTED = HttpServletResponse.SC_ACCEPTED; public static final int NON_AUTHORITATIVE_INFORMATION = HttpServletResponse.SC_NON_AUTHORITATIVE_INFORMATION; public static final int NO_CONTENT = HttpServletResponse.SC_NO_CONTENT; public static final int RESET_CONTENT = HttpServletResponse.SC_RESET_CONTENT; public static final int PARTIAL_CONTENT = HttpServletResponse.SC_PARTIAL_CONTENT; public static final int MULTIPLE_CHOICES = HttpServletResponse.SC_MULTIPLE_CHOICES; public static final int MOVED_PERMANENTLY = HttpServletResponse.SC_MOVED_PERMANENTLY; public static final int MOVED_TEMPORARILY = HttpServletResponse.SC_MOVED_TEMPORARILY; public static final int FOUND = HttpServletResponse.SC_FOUND; public static final int SEE_OTHER = HttpServletResponse.SC_SEE_OTHER; public static final int NOT_MODIFIED = HttpServletResponse.SC_NOT_MODIFIED; public static final int USE_PROXY = HttpServletResponse.SC_USE_PROXY; public static final int TEMPORARY_REDIRECT = HttpServletResponse.SC_TEMPORARY_REDIRECT; public static final int BAD_REQUEST = HttpServletResponse.SC_BAD_REQUEST; public static final int UNAUTHORIZED = HttpServletResponse.SC_UNAUTHORIZED; public static final int PAYMENT_REQUIRED = HttpServletResponse.SC_PAYMENT_REQUIRED; public static final int FORBIDDEN = HttpServletResponse.SC_FORBIDDEN; public static final int NOT_FOUND = HttpServletResponse.SC_NOT_FOUND; public static final int METHOD_NOT_ALLOWED = HttpServletResponse.SC_METHOD_NOT_ALLOWED; public static final int NOT_ACCEPTABLE = HttpServletResponse.SC_NOT_ACCEPTABLE; public static final int PROXY_AUTHENTICATION_REQUIRED = HttpServletResponse.SC_PROXY_AUTHENTICATION_REQUIRED; public static final int REQUEST_TIMEOUT = HttpServletResponse.SC_REQUEST_TIMEOUT; public static final int CONFLICT = HttpServletResponse.SC_CONFLICT; public static final int GONE = HttpServletResponse.SC_GONE; public static final int LENGTH_REQUIRED = HttpServletResponse.SC_LENGTH_REQUIRED; public static final int PRECONDITION_FAILED = HttpServletResponse.SC_PRECONDITION_FAILED; public static final int REQUEST_ENTITY_TOO_LARGE = HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE; public static final int REQUEST_URI_TOO_LONG = HttpServletResponse.SC_REQUEST_URI_TOO_LONG; public static final int UNSUPPORTED_MEDIA_TYPE = HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE; public static final int REQUESTED_RANGE_NOT_SATISFIABLE = HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE; public static final int EXPECTATION_FAILED = HttpServletResponse.SC_EXPECTATION_FAILED; public static final int INTERNAL_SERVER_ERROR = HttpServletResponse.SC_INTERNAL_SERVER_ERROR; public static final int NOT_IMPLEMENTED = HttpServletResponse.SC_NOT_IMPLEMENTED; public static final int BAD_GATEWAY = HttpServletResponse.SC_BAD_GATEWAY; public static final int SERVICE_UNAVAILABLE = HttpServletResponse.SC_SERVICE_UNAVAILABLE; public static final int GATEWAY_TIMEOUT = HttpServletResponse.SC_GATEWAY_TIMEOUT; public static final int HTTP_VERSION_NOT_SUPPORTED = HttpServletResponse.SC_HTTP_VERSION_NOT_SUPPORTED; public static final int SC100_CONTINUE = HttpServletResponse.SC_CONTINUE; public static final int SC101_SWITCHING_PROTOCOLS = HttpServletResponse.SC_SWITCHING_PROTOCOLS; public static final int SC200_OK = HttpServletResponse.SC_OK; public static final int SC201_CREATED = HttpServletResponse.SC_CREATED; public static final int SC202_ACCEPTED = HttpServletResponse.SC_ACCEPTED; public static final int SC203_NON_AUTHORITATIVE_INFORMATION = HttpServletResponse.SC_NON_AUTHORITATIVE_INFORMATION; public static final int SC204_NO_CONTENT = HttpServletResponse.SC_NO_CONTENT; public static final int SC205_RESET_CONTENT = HttpServletResponse.SC_RESET_CONTENT; public static final int SC300_MULTIPLE_CHOICES = HttpServletResponse.SC_MULTIPLE_CHOICES; public static final int SC301_MOVED_PERMANENTLY = HttpServletResponse.SC_MOVED_PERMANENTLY; public static final int SC302_MOVED_TEMPORARILY = HttpServletResponse.SC_MOVED_TEMPORARILY; public static final int SC302_FOUND = HttpServletResponse.SC_FOUND; public static final int SC303_SEE_OTHER = HttpServletResponse.SC_SEE_OTHER; public static final int SC304_NOT_MODIFIED = HttpServletResponse.SC_NOT_MODIFIED; public static final int SC305_USE_PROXY = HttpServletResponse.SC_USE_PROXY; public static final int SC307_TEMPORARY_REDIRECT = HttpServletResponse.SC_TEMPORARY_REDIRECT; public static final int SC400_BAD_REQUEST = HttpServletResponse.SC_BAD_REQUEST; public static final int SC401_UNAUTHORIZED = HttpServletResponse.SC_UNAUTHORIZED; public static final int SC402_PAYMENT_REQUIRED = HttpServletResponse.SC_PAYMENT_REQUIRED; public static final int SC403_FORBIDDEN = HttpServletResponse.SC_FORBIDDEN; public static final int SC404_NOT_FOUND = HttpServletResponse.SC_NOT_FOUND; public static final int SC405_METHOD_NOT_ALLOWED = HttpServletResponse.SC_METHOD_NOT_ALLOWED; public static final int SC406_NOT_ACCEPTABLE = HttpServletResponse.SC_NOT_ACCEPTABLE; public static final int SC407_PROXY_AUTHENTICATION_REQUIRED = HttpServletResponse.SC_PROXY_AUTHENTICATION_REQUIRED; public static final int SC408_REQUEST_TIMEOUT = HttpServletResponse.SC_REQUEST_TIMEOUT; public static final int SC409_CONFLICT = HttpServletResponse.SC_CONFLICT; public static final int SC410_GONE = HttpServletResponse.SC_GONE; public static final int SC411_LENGTH_REQUIRED = HttpServletResponse.SC_LENGTH_REQUIRED; public static final int SC412_PRECONDITION_FAILED = HttpServletResponse.SC_PRECONDITION_FAILED; public static final int SC413_REQUEST_ENTITY_TOO_LARGE = HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE; public static final int SC414_REQUEST_URI_TOO_LONG = HttpServletResponse.SC_REQUEST_URI_TOO_LONG; public static final int SC415_UNSUPPORTED_MEDIA_TYPE = HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE; public static final int SC416_REQUESTED_RANGE_NOT_SATISFIABLE = HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE; public static final int SC417_EXPECTATION_FAILED = HttpServletResponse.SC_EXPECTATION_FAILED; public static final int SC500_INTERNAL_SERVER_ERROR = HttpServletResponse.SC_INTERNAL_SERVER_ERROR; public static final int SC501_NOT_IMPLEMENTED = HttpServletResponse.SC_NOT_IMPLEMENTED; public static final int SC502_BAD_GATEWAY = HttpServletResponse.SC_BAD_GATEWAY; public static final int SC503_SERVICE_UNAVAILABLE = HttpServletResponse.SC_SERVICE_UNAVAILABLE; public static final int SC504_GATEWAY_TIMEOUT = HttpServletResponse.SC_GATEWAY_TIMEOUT; public static final int SC505_HTTP_VERSION_NOT_SUPPORTED = HttpServletResponse.SC_HTTP_VERSION_NOT_SUPPORTED; } /** * Type characterizes a response. * @see #getType() */ public enum Type { /** * Normal response. */ NORMAL, /** * An error has been sent. */ ERROR, /** * A redirect was sent. */ REDIRECT, } /** * An Enum to categorize how response content is produced. */ public enum ContentAccess { /** * Neither #getContentWriter() nor #getContentStream() was called. */ NONE, /** * A TemplateWriter is used to produce the response body. * @see #getContentWriter() */ WRITER, /** * A OutputStream is used to produce the response body. * @see #getContentStream() */ OUTPUTSTREAM } /** * Returns the request. */ @Override public abstract Request getRequest(); /** * Implements ResponseProvider and returns this. */ @Override default public Response getResponse() { return this; } /** * Returns the context. */ @Override default public Context getContext() { return getApplication().getContext(); } /** * Returns the application. */ @Override default public Application getApplication() { return getRequest().getApplication(); } /** * Returns if the response is committed. A response is committed * if status and headers are already written. * If not committed then a call to {@link #reset()} or {@link Response#resetBuffer()} * is still possible. */ public abstract boolean isCommitted(); /** * Clears the response buffer, the status code, and headers. * @throws IllegalStateException if the response has already been committed * @see #isCommitted() */ public abstract void reset(); /** * Returns the response type. The initial type is Type.NORMAL. * When you send an error or forward, the type changes * accordingly. */ public abstract Type getType(); /** * Adds a cookie to the response. */ public abstract void addCookie(Cookie cookie); /** * Adds the session id to the URL and returns the new URL. * The {@link Url} class calls this method automatically if you use it to build a URL string. * In Servlet terms this method corresponds to HttpServletRequest#encodeURL. * @return the URL with session id included or the original URL if the session id does not * need to be included in the url. */ public abstract String addSessionId(String url); /** * Returns a UriEncoder object which can be used to percent encode all characters of URLs or URIs which * are reserved characters and may not be used in a URL. */ public abstract UriEncoder getUriEncoder(); //------------------------------ // locale //------------------------------ /** * Returns the locale data associated with the response. * The locale data can be set explicitly by {@link #setLocaleService(LocaleService)}. * If not explicitly set it is the same as the LocalData of the request. * @see Request#getLocaleService() */ @Override public abstract LocaleService getLocaleService(); /** * Sets the locale data associated with the response. */ public abstract void setLocaleService(LocaleService service); /** * Sets the locale data associated with the response. */ default public void setLocaleService(Locale locale) { setLocaleService(getApplication().getLocaleServices().getService(locale)); } /** * Shortcut for {@link #getLocaleService()}.getSerializer(). */ default public LocaleSerializer getLocaleSerializer() { return getLocaleService().getSerializer(); } //------------------------------ // status //------------------------------ /** * Returns the status code of the response. */ public abstract int getStatus(); /** * Sets the status code of the response. * @param statusCode the code. See {@link Status} for a list of common status codes. */ public abstract void setStatus(int statusCode); //------------------------------ // error //------------------------------ /** * Sends an error response to the client. * This is a shortcut for {@link #sendError(int, String, Throwable) sendError(statusCode, null, null)}. */ default public void sendError(int statusCode) throws IllegalStateException, IOException { sendError(statusCode, null, null); } /** * Sends an error response to the client. * After using this method, the response is committed and should not be written to. * Internally the {@link ErrorResponse} implementation provided by {@link Application#createErrorResponse()} * is used to write the response. * @param statusCode see {@link Status} for a list of status codes. * @param message if not null, the message will be included in the error response. * @param error an optional error object. * @throws IllegalStateException if the response is already committed */ public abstract void sendError(int statusCode, String message, Throwable error) throws IllegalStateException, IOException; //------------------------------ // sendRedirect //------------------------------ /** * Sends a redirect response to the client. * After using this method, the response is committed and should not be written to. * @throws IllegalStateException if the response has already been committed */ public abstract void sendRedirect(String url) throws IOException; /** * Sends a redirect response. * After using this method, the response is committed and should not be written to. * @throws IllegalStateException if the response has already been committed */ default public void sendRedirect(Url url) throws IOException { sendRedirect(url.toString()); } /** * Sends a redirect response to the given Resource. * After using this method, the response is committed and should not be written to. * @throws IllegalStateException if the response has already been committed */ default public void sendRedirect(Resource resource) throws IOException { sendRedirect(new Url(this, resource)); } /** * Sends a redirect response to the Resource whose controller has the given * class. * After using this method, the response is committed and should not be written to. * @throws IllegalStateException if the response has already been committed */ default public <C extends Controller> void sendRedirect(Class<C> controllerClass) throws IOException { sendRedirect(new Url(this, controllerClass)); } //------------------------------ // write //------------------------------ /** * Writes a template to the response content. * @param template a template object. If null, the method does nothing. Else * it calls {@link Template#print(TemplateWriter)}, passing the * {@link #getContentWriter() content writer} of this response. */ default public void writeTemplate(Template template) throws Exception { writeContent(template, null); } /** * Writes JSON data to the response content. * @param object a object which is converted to JSON. */ default public void writeJson(Object object) throws Exception { writeContent(object, ContentType.APPLICATION_JSON); } /** * Writes XML data to the response content. * In order to use this feature, you need to configure and add * {@link JaxbXmlSerializer} or another suitable ContentSerializer to the applications * {@link AppConfig#getContentSerializers() serializers} * during application setup. */ default public void writeXml(Object object) throws Exception { writeContent(object, ContentType.APPLICATION_XML); } /** * Writes text to the response content. */ default public void writeText(String text) throws Exception { writeContent(text, ContentType.TEXT_PLAIN); } /** * Calls write(object, null); */ default public void writeContent(Object object) throws Exception { writeContent(object, null); } /** * Writes data to the response content. * If the object parameter is null, the method does nothing.<br> * If the object parameter is a template, then {@link #writeTemplate(Template)} is called.<br> * If the provided content-type is null, then the current content-type of the response * is used. (The response content-type may have been set during content negotiation).<br> * If the current content-type is null and the object is a string the content-type * is set to text/plain. Else an exception is raised.<br> * The content-type of the response is then set to the calculated content-type. * and the object is written to the response using a {@link ContentSerializer} for * that content-type. * @param object a object containing the data. If null the method does nothing. * @param contentType a content type. Can be null, if the content-type was already set on * the response */ public abstract void writeContent(Object object, ContentType contentType) throws Exception; //----------------------------------- // response content //----------------------------------- /** * Returns how response content is produced. */ public abstract ContentAccess getContentAccess(); /** * Returns a writer to write textual output. * If no content character encoding has been set, * the encoding of the application is used. * The method may not be called if {@link #getContentStream()} was called before. * @see Application#getEncoding() */ public abstract TemplateWriter getContentWriter() throws IOException; /** * Returns a OutputStream to write a binary response. * The method may not be called if {@link #getContentWriter()} was called before. */ public abstract OutputStream getContentStream() throws IOException; /** * Sets the content type of the response content. * If the content type is null or the response has been committed, it is ignored. */ public abstract void setContentType(ContentType contentType); /** * Returns the content type used for the response content. If the request * was dispatched to a resource using content negotiation, then * the content type will be initialized to that negotiated content type. * Else it is initialized to null. */ public abstract ContentType getContentType(); /** * Returns the content type used for the response content as string, * appended by the content encoding. * @see #getContentType() * @see #getContentEncoding() */ default public String getContentTypeAndEncoding() { ContentType contentType = getContentType(); if (contentType != null) { String result = contentType.getValue(); String encoding = getContentEncoding(); if (encoding != null) result += "; charset=" + encoding; return result; } else return null; } /** * Sets the character encoding. * If the response has been committed or {@link #getContentWriter()} has been called, it has no effect */ public abstract void setContentEncoding(String encoding); /** * Returns the character encoding of the response. * The encoding can be set with a call to {@link #setContentEncoding(String)}. * If getWriter() is called and no encoding is set, then the default encoding of the application * (see {@link Application#getEncoding()} is used. */ public abstract String getContentEncoding(); /** * Sets the content length. */ public abstract void setContentLength(long length); /** * Sets the content language. */ public abstract void setContentLanguage(Locale locale); /** * Returns the content language. * @return the locale defining the content language, or null if not * set. When a Writer is requested, and the content language is null, * then it is set to locale of the response's lcaleData. * @see #getLocaleService() */ public abstract Locale getContentLanguage(); /** * Close the content output, either the {@link #getContentStream() OutputStream} * or {@link #getContentWriter() TemplateWriter}. * This method is called automatically at the end of request {@link Application#process(Request) processing}. */ public abstract void closeContent(); /** * Adds a ResponseInterceptor which can wrap the Response OutputStream. * {@link #getContentStream()} or {@link #getContentWriter()} must not have been called yet. */ public abstract void addInterceptor(ResponseStreamInterceptor interceptor); /** * Adds a ResponseWriterInterceptor which can wrap the Response Writer. * {@link #getContentStream()} or {@link #getContentWriter()} must not have been called yet. */ public abstract void addInterceptor(ResponseWriterInterceptor interceptor); //----------------------------------- // buffer //----------------------------------- /** * Resets the output buffer, discarding any buffered content. */ public void resetBuffer(); /** * Flushes any buffered content. */ public void flushBuffer() throws IOException; /** * Sets the buffer size. */ public void setBufferSize(int size); /** * Returns the buffer size. */ public int getBufferSize(); //----------------------------------- // response headers //----------------------------------- /** * Returns the ResponseHeaders object which allows to add or set headers. */ public abstract ResponseHeaders getHeaders(); //----------------------------------- // misc //----------------------------------- /** * Prints response info to the PrintStream. */ default public void print(PrintStream out) { Check.notNull(out, "out"); print(new PrintWriter(out, true)); } /** * Prints response info to the PrintWriter. */ default public void print(PrintWriter out) { Check.notNull(out, "out"); out.println(getStatus()); ResponseHeaders headers = getHeaders(); for (String name : headers) { String[] values = headers.getAll(name); for (String value : values) { out.print(name); out.print(' '); out.println(value); } } } /** * Returns the underlying implementation of the response which has the given class * or null, if the implementation has a different class. * In an ServletEnvironment the underlying implementation is a HttpServletResponse. */ public abstract <T> T unwrap(Class<T> implClass); }