/* * 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.InputStream; import java.io.PrintStream; import java.io.PrintWriter; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.util.Iterator; import java.util.Locale; import java.util.Map; import org.civilian.content.ContentSerializer; import org.civilian.content.ContentType; import org.civilian.content.ContentTypeList; import org.civilian.provider.*; import org.civilian.request.*; import org.civilian.resource.ExtensionMapping; import org.civilian.resource.Path; import org.civilian.resource.PathParam; import org.civilian.resource.Url; import org.civilian.text.LocaleService; import org.civilian.type.Type; import org.civilian.type.fn.LocaleSerializer; import org.civilian.type.fn.StandardSerializer; import org.civilian.util.Check; import org.civilian.util.ClassUtil; import org.civilian.util.Value; /** * Request represents a request for an application resource.<p> * A request consists of * <ul> * <li>a request path, designating a resource, * <li>a request method (like GET, POST, etc), * <li>a list of headers, * <li>preferences for the accepted response content type and locale, * <li>an optional request content, * <li>info about remote interface, server and local interface. * <li>security info * </ul> * Each request is associated with a locale, which is either the preferred * locale or a locale explicitly set by the application (for example, a locale stored in a user * profile).<p> * In a Servlet environment Request is functionally equivalent to a HttpServletRequest. * Many methods of HttpServletRequest have direct counterparts in Request. * Others methods are bundled to functional groups and available via sub-objects defined * in package org.civilian.request: * <ul> * <li>Header related methods: in {@link RequestHeaders} by {@link #getHeaders()} * <li>Security related methods: in {@link RequestSecurity} by {@link #getSecurity()} * <li>etc. * </ul> */ public interface Request extends RequestProvider, ResponseProvider, ApplicationProvider, ContextProvider, PathParamProvider, PathProvider, LocaleServiceProvider { /** * Defines constants for common request methods as defined by the HTTP protocol. */ public static interface Method { public static final String CONNECT = "CONNECT"; public static final String DELETE = "DELETE"; public static final String GET = "GET"; public static final String HEAD = "HEAD"; public static final String OPTIONS = "OPTIONS"; public static final String PATCH = "PATCH"; public static final String POST = "POST"; public static final String PUT = "PUT"; public static final String TRACE = "TRACE"; } /** * An Enum to categorize how the request content was accessed. * @see #getContentAccess() */ public enum ContentAccess { /** * Neither a {@link Request#getContentStream() InputStream} nor a * {@link Request#getContentReader() Reader} were obtained yet. */ NONE, /** * {@link Request#getContentStream()} was called. */ INPUTSTREAM, /** * {@link Request#getContentReader()} was called. */ READER } //----------------------------- // general accessors //----------------------------- /** * Implements RequestProvider and returns this. */ @Override default public Request getRequest() { return this; } /** * Returns the associated response. */ @Override public Response getResponse(); /** * Allows to set the response. * The {@link Response#getRequest() request} of the response must equal this. * Use this method if you need to wrap the original response to intercept * invocation of response methods. */ public void setResponse(Response response); /** * Returns the context to which this request belongs. */ @Override default public Context getContext() { return getApplication().getContext(); } /** * Returns the application to which this request belongs. */ @Override public Application getApplication(); /** * Returns the name of the method with which this request was made. * Examples are "GET", "PUT", "POST", etc. */ public String getMethod(); /** * Returns if the request has the given method. * @see #getMethod() */ default public boolean hasMethod(String method) { return ClassUtil.equals(getMethod(), method); } //----------------------------- // path //----------------------------- /** * Returns the absolute path of this request. */ @Override public Path getPath(); /** * Returns the path of this request relative to the application path. * @see Application#getPath() */ @Override public Path getRelativePath(); /** * Returns the real path on the server which corresponds to the request path * @return the path or null, if there is no correspondence. * @see Context#getRealPath(String) */ default public String getRealPath() { Context context = getContext(); Path subContextPath = getPath().cutStart(context.getPath()); return subContextPath != null ? context.getRealPath(subContextPath) : null; } /** * Returns the request path in its original form, as specified by the client. * For a HTTP request this equals the part of the request URL from the protocol * name up to the query string in the first line of the HTTP request. */ public String getOriginalPath(); /** * Returns a Url object for the request. * If the request was completely matched against a {@link Resource}, it is constructed * from that Resource, and all path params of the request are set in the URL. * Else it is constructed from the request path. * @param addServer should protocol, host and port be included in the URL? * @param addParams should parameters added as query parameter? */ default public Url getUrl(boolean addServer, boolean addParams) { Url url; if (getResource() != null) url = new Url(this, getResource()); // also copies the path params else url = new Url(this, getPath()); if (addServer) url.prepend(getServerInfo().toString()); if (addParams) { for (Iterator<String> pnames = getParameterNames(); pnames.hasNext(); ) { String pname = pnames.next(); url.addQueryParams(pname, getParameters(pname)); } } return url; } //---------------------------- // resource //---------------------------- /** * Returns the resource associated with request. The resource * is determined during resource dispatch. It returns null, * if resource dispatch has not run yet, or no resource corresponds * to the request path. */ public Resource getResource(); /** * Sets the resource associated with request. The resource * is automatically set when detected during resource dispatch. */ public void setResource(Resource resource); //---------------------------- // attributes //---------------------------- /** * Returns an attribute which was previously associated with the request. * @param name the attribute name * @see #setAttribute(String, Object) */ public Object getAttribute(String name); /** * Returns an iterator for the attribute names stored in the request. */ public Iterator<String> getAttributeNames(); /** * Stores an attribute under the given name in the request. */ public void setAttribute(String name, Object value); //---------------------------- // cookies //---------------------------- /** * Returns the CookieList containing all the Cookies sent with this request. * The method returns an empty list if no cookies were sent. */ public CookieList getCookies(); //---------------------------- // parameters //---------------------------- /** * Returns the first value of a request parameter, or null if the parameter does not exist. * In HTTP terms this includes query parameters contained in the request URL * and also post parameters (i.e. parameters contained in the request content with * content type application/x-www-form-urlencoded). */ public String getParameter(String name); /** * Returns the value of a parameter wrapped in a Value object. * If the parameter does not exist, the Value is {@link Value#hasValue() empty}. * If the conversion of the parameter to the requested type fails, the * Value contains the {@link Value#getError() conversion error}. */ default public <T> Value<T> getParameter(String name, Type<T> type) { return new Value<>(type, getParameter(name), StandardSerializer.INSTANCE); } /** * Returns a String array containing all values of a request parameter. * @return the values or an empty array with length 0 if the parameter does not exist. */ public String[] getParameters(String name); /** * Returns an Iterator for the names of the parameters contained in this request. */ public Iterator<String> getParameterNames(); /** * Returns a map of all request parameter names and values. */ public Map<String,String[]> getParameterMap(); //---------------------------- // path params //---------------------------- /** * Sets the value of a path parameter. * This is usually automatically done during resource dispatch, when * the request path is parsed, and path segments are recognized to match * defined PathParams. */ public <T> void setPathParam(PathParam<T> pathParam, T value); /** * Sets the path parameters. * This is usually automatically done during resource dispatch, when * the request path is parsed, and path segments are recognized to match * defined PathParams. * All previous path parameters are cleared. */ public void setPathParams(Map<PathParam<?>,Object> pathParams); /** * Returns the value of a path parameter, or null if the path parameter is not contained * in the request. */ @Override public <T> T getPathParam(PathParam<T> pathParam); /** * Returns an Iterator for all path parameters recognized in this request. */ public Iterator<PathParam<?>> getPathParams(); //---------------------------- // matrix params //---------------------------- /** * Returns the value of a matrix parameter, or null if such a matrix parameter does not exist. * Matrix parameters are only recognized in the last segment of the request path. */ public String getMatrixParam(String name); /** * Returns the values of a matrix parameter as a string array. * @return the array. The array is empty if the matrix parameter is not contained * in this request. */ public String[] getMatrixParams(String name); /** * Returns the value of a matrix parameter wrapped in a Value object. * If the parameter does not exist, the Value is empty. * If the conversion of the parameter to the requested type fails, the * Value contains the error. */ default public <T> Value<T> getMatrixParam(String name, Type<T> type) { return new Value<>(type, getMatrixParam(name), StandardSerializer.INSTANCE); } /** * Returns an Iterator of matrix parameter names contained in this request. */ public Iterator<String> getMatrixParamNames(); //---------------------------- // uploads //---------------------------- /** * Returns if the request contains uploads. */ public boolean hasUploads(); /** * Returns the first Upload object with the given name. * In Servlet terms the Upload object corresponds to a javax.servlet.http.Part object * in a multipart/form-data request whose content disposition contains * a filename parameter and whose name equals the given name. */ public Upload getUpload(String name); /** * Returns all Upload objects with the given name. * In Servlet terms the Upload object corresponds to a javax.servlet.http.Part object * in a multipart/form-data request whose content disposition contains * a filename parameter and whose name equals the given name. */ public Upload[] getUploads(String name); /** * Returns an exception if the request contains uploaded files and * one ore more files violated constraints defined by the {@link Application#getUploadConfig() UploadConfig}. * In this case the request parameters may not be properly initialized. * Therefore for upload requests you should check upload errors first, before you evaluate request parameters. */ public Exception getUploadError(); /** * Returns an iterator for all Upload names. * @see #getUpload(String) */ public Iterator<String> getUploadNames(); //------------------------------ // locale //------------------------------ /** * Returns the locale data associated with the request. * The locale data can be set explicitly by {@link #setLocaleService(LocaleService)}. * If not explicitly set, it is derived from the preferred locale ({@link Request#getAcceptedLocale()}). * If the preferred locale is not contained in the list of supported locales (see {@link Application#getLocaleServices()}) * then the default application locale will be used. */ @Override public LocaleService getLocaleService(); /** * Sets the locale data associated with the request. * This overrides the default locale data, as defined by the preferred locale. * A use case is to set the locale according to the preferences stored in a user session. */ public void setLocaleService(LocaleService service); /** * Sets the locale data associated with the request to the locale data with * the given locale */ default public void setLocaleService(Locale locale) { Check.notNull(locale, "locale"); setLocaleService(getApplication().getLocaleServices().getService(locale)); } /** * Returns a the LocaleSerializer for the current locale data. * Shortcut for getLocaleService().getSerializer(). */ default public LocaleSerializer getLocaleSerializer() { return getLocaleService().getSerializer(); } /** * Returns a list of qualified response content types accepted by the client. * This list is defined by the Accept header. * If the request does not specify an explicit list of accepted content-types * and {@link ExtensionMapping extension mappings} are configured, then * the extension of the path is used to determine the accepted content type. * Else a list with the single content-type */* is returned. */ public ContentTypeList getAcceptedContentTypes(); /** * Returns the preferred Locale for response content.<br> * This Locale is defined by the Accept-Language header.<br> * If the request does not contain a preferred locale and {@link ExtensionMapping extension locales} * are configured, then the extension of the path is used to determine the accepted locale. * Else the default locale of the application is returned. (Note: This differs from * a servlet container which would return the default system locale). */ public abstract Locale getAcceptedLocale(); /** * Returns the preferred Locales for returned content, in decreasing order. * This list is defined by the Accept-Language header.<br> */ public abstract Iterator<Locale> getAcceptedLocales(); //-------------------------------------- // content //-------------------------------------- /** * Returns the character encoding used for the request content, or * null if not specified. */ public String getContentEncoding(); /** * Explicitly sets the character encoding of the content. */ public void setContentEncoding(String encoding) throws UnsupportedEncodingException; /** * Returns the length of the content in bytes, or -1 if not known. */ public long getContentLength(); /** * Returns the content type of the request content or null if not known. */ public ContentType getContentType(); /** * Overrides the content type of the request. */ public void setContentType(ContentType contentType); /** * Returns how the request content was accessed. */ public ContentAccess getContentAccess(); /** * Returns an InputStream to read the request content. * @throws IllegalArgumentException if {@link #getContentReader()} has already been called. */ public abstract InputStream getContentStream() throws IOException; /** * Returns a Reader for the request content. If the {@link #getContentEncoding() content encoding} * is not set, it uses the default {@link Application#getEncoding() application encoding}. * @see Application#getEncoding() * @throws IllegalArgumentException if {@link #getContentStream()} has already been called. */ public Reader getContentReader() throws IOException; /** * Reads the content and transforms it into an Object of the given type. * This method is a shortcut for readContent(type, type). */ default public <T> T readContent(Class<T> type) throws Exception { return readContent(type, type); } /** * Reads the content and transforms it into an Object of the given type. * Internally a ContentSerializer defined by the application is used * to read the content. * @param type the type of the expected object * @param genericType the generic type of the expected object or null not known * @throws Exception if reading throws a runtime exception or no suitable * content reader is available. * The exception is a {@link BadRequestException} if the ContentSerializer * recognized bad syntax in the content. * @see Application#getContentSerializer(ContentType) * @see ContentSerializer#read(Class, java.lang.reflect.Type, Reader) */ default public <T> T readContent(Class<T> type, java.lang.reflect.Type genericType) throws BadRequestException, Exception { Check.notNull(type, "type"); if (genericType == null) genericType = type; ContentType contentType = getContentType(); if ((contentType == null) && (type == String.class)) contentType = ContentType.TEXT_PLAIN; ContentSerializer reader = getApplication().getContentSerializer(contentType); if (reader == null) throw new IllegalStateException("don't know how to read content with content type '" + contentType + "'"); try { return reader.read(type, genericType, getContentReader()); } catch(Exception e) { String message = reader.describeReadError(e); if (message != null) throw new BadRequestException("RequestContent: " + message, e); else throw e; } } /** * Adds a RequestStreamInterceptor which can wrap the request {@link #getContentStream() InputStream}. * {@link #getContentStream()} or {@link #getContentReader()} must not have been called yet. */ public abstract void addInterceptor(RequestStreamInterceptor interceptor); /** * Adds a RequestReaderInterceptor which can wrap the request {@link #getContentReader() Reader}. * {@link #getContentStream()} or {@link #getContentReader()} must not have been called yet. */ public abstract void addInterceptor(RequestReaderInterceptor interceptor); //-------------------------------------- // async operations //-------------------------------------- /** * Returns the AsyncContext that was created by the most recent call to {@link #startAsync()} * @return the AsnycContext * @throws IllegalStateException if startAsync() has not been called. */ public AsyncContext getAsyncContext(); /** * Returns if this request has been put into asynchronous mode by a call to {@link #startAsync()}. */ public boolean isAsyncStarted(); /** * Returns if this request supports asynchronous mode. */ public boolean isAsyncSupported(); /** * Puts this request into asynchronous mode, and initializes its AsyncContext. * @throws IllegalStateException if this request does not support asynchronous operations or if called again * in a state where the AsyncContext intervenes, or when the response has been closed. */ public AsyncContext startAsync(); //-------------------------------------- // detail objects //-------------------------------------- /** * Returns the request headers. */ public RequestHeaders getHeaders(); /** * Returns a RequestSecurity object which provides security related * information. */ public RequestSecurity getSecurity(); /** * Returns a ServerInfo object which provides server related * information. */ public ServerInfo getServerInfo(); /** * Returns a RemoteInfo object which provides information about * the remote site of the request. */ public RemoteInfo getRemoteInfo(); /** * Returns a LocalInfo object which provides information about * the local interface which received the request. */ public LocalInfo getLocalInfo(); /** * Returns the session of the request. * @param create if true and the request does not have a session, a session is created * @return the session or null, if the request does not have a session and create is false. */ public Session getSession(boolean create); //-------------------------------------- // misc //-------------------------------------- /** * Prints request info to the PrintStream. */ default public void print(PrintStream out) { Check.notNull(out, "out"); print(new PrintWriter(out, true)); } /** * Prints request info to the PrintWriter. */ default public void print(PrintWriter out) { Check.notNull(out, "out"); out.print(getMethod()); out.print(" "); out.println(getUrl(false /*server*/, true /*params*/)); RequestHeaders 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 request which has the given class * or null, if the implementation has a different class. * In a servlet environment Request wraps a HttpServletRequest. */ public <T> T unwrap(Class<T> implClass); }