/* * 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 org.civilian.annotation.Consumes; import org.civilian.annotation.Get; import org.civilian.annotation.Post; import org.civilian.annotation.Produces; import org.civilian.annotation.RequestMethod; import org.civilian.controller.ControllerMethod; import org.civilian.controller.ControllerType; import org.civilian.controller.NegotiatedMethod; import org.civilian.internal.Logs; import org.civilian.provider.ApplicationProvider; import org.civilian.provider.ContextProvider; import org.civilian.provider.MessageProvider; import org.civilian.provider.RequestProvider; import org.civilian.provider.ResponseProvider; import org.civilian.text.LocaleService; import org.civilian.text.msg.MsgBundle; import org.civilian.util.Check; /** * Controller handles requests for a {@link Resource}. * All controller classes of an application must be derived from Controller. * A Controller object is instantiated to process a single request and is then * discarded. Therefore controllers don't need to be threadsafe * and may declare own properties and use them to * process the request. The Controller class itself has several properties, notably * for the {@link #getRequest() request} and {@link #getResponse() response}. * <p> * Controllers define action methods which represent different ways to handle a request, * depending on request properties. * An action method is a * <ul> * <li>non-static method * <li>with return type void * <li>annotated with a {@link RequestMethod} annotation or one or more of the abbreviated * method annotations like {@link Get}, {@link Post} etc. * </ul> * <p> * Additionally an action method can be annotated with {@link Consumes} and {@link Produces} * annotations, to signal which request content can be processed and/or which response * content can be produced. * <p> * Given an incoming request, the best matching action method is selected by an algorithm * called content negotiation. If no action method matches the request, an error * is sent (e.g. {@link Response.Status#SC405_METHOD_NOT_ALLOWED}, * {@link Response.Status#SC406_NOT_ACCEPTABLE}, * {@link Response.Status#SC415_UNSUPPORTED_MEDIA_TYPE} as response. * Else the matching method is invoked.<br> * An action method can have annotated parameters into which context values, like * request parameters, matrix parameters, * headers, etc. are injected when the method is invoked. * <p> * Before and after the invocation of the negotiated action method several * predefined controller methods are called, * to allow the controller to properly initialize and shutdown before and after request processing. * The exact invocation order is as follows: * <pre><code> * {@link #checkAccess() checkAccess} * try * if request is not committed * [actionMethod,error] = find matching action method * if actionMethod is null * {@link #reject(int) reject(error)} * else * {@link #init()} * {@link #setCaching()} * if request is not committed * call action method * catch exception * {@link #onError(Exception) onError(exception)} * finally * {@link #exit()} * </code></pre> * All of the above methods except exit() allow to throw any exception. * If you decide not to handle an exception in your controller * implementation, then the exception is passed to {@link Application#onError(Request, Throwable)} * for application-wide error handling. */ public class Controller implements MessageProvider, RequestProvider, ResponseProvider, ApplicationProvider, ContextProvider { //------------------------------------ // accessors //------------------------------------ /** * Returns the develop flag of the application. * @see Application#develop */ public boolean develop() { return getApplication().develop(); } /** * Returns the context to which the application belongs. * @see Application#getContext() */ @Override public Context getContext() { return getApplication().getContext(); } /** * Returns the application to which the controller belongs. */ @Override public Application getApplication() { return getRequest().getApplication(); } /** * Returns the application, casted to the given application class. */ public <A extends Application> A getApplication(Class<A> appClass) { return appClass.cast(getApplication()); } /** * Returns the request. */ @Override public Request getRequest() { checkProcessing(); return request_; } private void setRequest(Request request) { Check.notNull(request, "request"); if (request_ != null) throw new IllegalStateException("already processing"); request_ = request; } /** * Returns the response. */ @Override public Response getResponse() { return getRequest().getResponse(); } /** * Returns the type of the controller. */ public ControllerType getControllerType() { return type_; } /** * Sets the ControllerType. This is automatically done * when a Controller is created during resource dispatch. */ public void setControllerType(ControllerType type) { Check.notNull(type, "type"); if (type.getControllerClass() != getClass()) throw new IllegalArgumentException(); type_ = type; } /** * Returns the LocaleService of the response. * Shortcut for getResponse().getLocaleService(). */ public LocaleService getLocaleService() { return getResponse().getLocaleService(); } /** * Returns the MsgBundle object of the LocaleService in the response. * Shortcut for getResponse().getLocaleService().getMsgBundle() */ public MsgBundle getMsgBundle() { return getLocaleService().getMsgBundle(); } /** * Translates a text key into a message, using * the MsgBundle object of the response. * @see #getMsgBundle() */ @Override public String msg(Object key) { return getMsgBundle().msg(key); } /** * Translates a text key into a message, using * the MsgBundle object of the response. * @param params parameters which are inserted into the message * at placeholder strings. * @see #getMsgBundle() */ @Override public String msg(Object key, Object... params) { return getMsgBundle().msg(key, params); } /** * Returns any uncatched exception thrown during request processing. * This is the same exception passed to {@link #onError(Exception)}. */ public Exception getException() { return exception_; } //------------------------------------ // processing //------------------------------------ /** * Processes a request. This method is called by the resource dispatch * on the controller defined for the resource which matches the request. * The ControllerType must have been {@link #setControllerType(ControllerType) initialized} * before this method can be called. * This method * <ul> * <li>calls the various init-methods * <li>selects the action method based on request properties * <li>invokes the action method * <li>calls the various exit-methods * </ul> */ public void process(Request request) throws Exception { process(request, null); } /** * Processes a request. * @param negMethod if not null, then directly use this action instead of negotiating the method */ public void process(Request request, NegotiatedMethod negMethod) throws Exception { setRequest(request); Response response = request.getResponse(); try { boolean debug = Logs.CONTROLLER.isDebugEnabled(); if (debug) Logs.CONTROLLER.debug(getClass().getName()); checkAccess(); if (!response.isCommitted()) { negMethod = negotiate(request, negMethod); if (negMethod.positive()) { response.setContentType(negMethod.getContentType()); init(); setCaching(); if (!response.isCommitted()) { ControllerMethod method = negMethod.getMethod(); if (debug) Logs.CONTROLLER.debug("#{}", method); method.invoke(this); } } else reject(negMethod.getError()); } } catch(Exception e) { exception_ = e; onError(e); } finally { exit(); request_ = null; } } private NegotiatedMethod negotiate(Request request, NegotiatedMethod method) { if (method == null) { if (type_ == null) throw new IllegalArgumentException("ControllerType not initialized"); method = type_.getMethod(request); } return method; } /** * Returns if the controller is processing a request. */ public boolean isProcessing() { return request_ != null; } private void checkProcessing() { if (request_ == null) throw new IllegalStateException("may not be called outside of process(Request)"); } /** * Called at the beginning of request processing. * The controller should check if access to the resource is allowed.<br> * Example: Controllers for resources who lie behind a login-wall * could allow access depending on whether there exists a valid session.<br> * If the resource does not allow access, it should either set * an appropriate response status code, or redirect the response.<br> * The controller can use the request or response object, but should not * rely on any other initializations. */ protected void checkAccess() throws Exception { } /** * Called when no action method matches the request. * The default implementation sends an response error. * @param error the suggested response error code (405, 406, 415) */ protected void reject(int error) throws Exception { getResponse().sendError(error); } /** * Initialize the controller. Called after {@link #checkAccess()} was called. * Derived classes can put common initialization code here. * The default implementation is empty. */ protected void init() throws Exception { } /** * Configures the caching behavior. * Called after {@link #init()} was called. * The default implementation sets the "Cache-Control" header to "no-cache". */ protected void setCaching() { getResponse().getHeaders().set("Cache-Control", "no-cache"); } /** * Called when an exception is thrown during request processing. * The default implementation just rethrows the exception which will then be delivered * to {@link Application#onError(Request, Throwable)}. * Derived controller implementations which want to handle errors should override this method. */ protected void onError(Exception e) throws Exception { throw e; } /** * Called at the end of request processing. */ protected void exit() { } private ControllerType type_; private Request request_; private Exception exception_; }