/*****************************************************************************
* Copyright 2011 Zdenko Vrabel
*
* 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 org.zdevra.guice.mvc;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServlet;
import org.zdevra.guice.mvc.ConversionService.ConverterFactory;
import org.zdevra.guice.mvc.annotations.Controller;
import org.zdevra.guice.mvc.converters.BooleanConverterFactory;
import org.zdevra.guice.mvc.converters.DateConverterFactory;
import org.zdevra.guice.mvc.converters.DoubleConverterFactory;
import org.zdevra.guice.mvc.converters.FloatConverterFactory;
import org.zdevra.guice.mvc.converters.IntegerConverterFactory;
import org.zdevra.guice.mvc.converters.LongConverterFactory;
import org.zdevra.guice.mvc.converters.StringConverterFactory;
import org.zdevra.guice.mvc.parameters.HttpPostParam;
import org.zdevra.guice.mvc.parameters.HttpSessionParam;
import org.zdevra.guice.mvc.parameters.InjectorParam;
import org.zdevra.guice.mvc.parameters.ModelParam;
import org.zdevra.guice.mvc.parameters.ParamProcessor;
import org.zdevra.guice.mvc.parameters.ParamProcessorFactory;
import org.zdevra.guice.mvc.parameters.ParamProcessorsService;
import org.zdevra.guice.mvc.parameters.RequestParam;
import org.zdevra.guice.mvc.parameters.RequestScopedAttributeParam;
import org.zdevra.guice.mvc.parameters.ResponseParam;
import org.zdevra.guice.mvc.parameters.SessionAttributeParam;
import org.zdevra.guice.mvc.parameters.UriParam;
import org.zdevra.guice.mvc.views.NamedViewScanner;
import org.zdevra.guice.mvc.views.RedirectViewScanner;
import com.google.inject.name.Names;
import com.google.inject.servlet.ServletModule;
/**
* <i>MVC module</i> for GUICE.
* <p>
*
* If you are fammiliar with the GUICE servlets, then usage of the Lime MVC is pretty straight forward.
* MvcModule is basically extended version of Guice's ServletModule and you can use all ServletModule's
* methods in configureControllers() method implementation (like serve() etc..).
* <p>
*
* In your web application, put new MvcModule with implemented configureControllers() method
* into GuiceServletContextListener implementation.
*
* <p>example:
* <pre class="prettyprint">
* public class WebAppConfiguration extends GuiceServletContextListener {
* ...
* protected Injector getInjector() {
* Injector injector = Guice.createInjector(
* new MvcModule() {
* protected void configureControllers() {
*
* control("/someController/*")
* .withController(SomeController.class)
* ...
* }
* }
* );
* return injector;
* }
* }
* </pre>
* Example shows the basic usage and registers the simple controller class.
* All requests starting with '/someController/' will be processed by the SomeController.
*
* <p>
* It is possible to have more controllers for one URL registration and then all controllers
* will be invoked. This is good when you have diplayed several independent informations in the view.
*
* <p>example:
* <pre class="prettyprint">
* public class WebAppConfiguration extends GuiceServletContextListener {
* ...
* protected Injector getInjector() {
* Injector injector = Guice.createInjector(
* new MvcModule() {
* protected void configureControllers() {
*
* control("/someControllers/*")
* .withController(SomeController.class)
* .withController(AnotherController.class);
* ...
* }
* }
* );
* return injector;
* }
* }
* </pre>
*
* In both controllers can be defined the same path. Let's assume 2 controllers:
* <p>
* <pre class="prettyprint">
* {@literal @}Controller
* {@literal @}View("some_view")
* public class SomeController {
* ...
* {@literal @}Path("/get") {@literal}Model("user")
* public User getUser() {
* ...
* }
* }
*
* {@literal @}Controller
* {@literal @}View("some_view")
* public class AnotherController {
* ...
* {@literal @}Path("/get") {@literal}Model("goods")
* public List<Product> getGoods() {
* ...
* }
* }
* </pre>
*
* In that case, both methods 'getUser' and 'getGoods' will be invoked. Be carrefull when you're invoking 2 or more methods. It may
* cause problems with multiple views when only first view is choosen.
* <p>
*
* @see Controller
* @see ModelMap
* @see ModelAndView
* @see ViewPoint
* @see ViewResolver
* @see ExceptionResolver
* @see ViewScannerService
*
*/
public abstract class MvcModule extends ServletModule {
private static final Logger logger = Logger.getLogger(MvcModule.class.getName());
private MultibinderBuilder<ConverterFactory> conversionServiceBuilder;
private ExceptionResolverBuilder exceptionResolverBuilder;
private ControllerModuleBuilder controllerModuleBuilder;
private MultibinderBuilder<ParamProcessorFactory> paramProcessorBuilder;
private MultibinderBuilder<ViewScanner> viewScannerBuilder;
private MultibinderBuilder<InterceptorHandler> interceptorHandlersBuilder;
private NamedViewBuilder namedViewBudiler;
private List<HttpServlet> servlets;
/**
* Put into this method your controllers configuration
*/
protected abstract void configureControllers();
/**
* This method initializate controller servlets
*/
@Override
protected final void configureServlets() {
if (controllerModuleBuilder != null) {
throw new IllegalStateException("Re-entry is not allowed.");
}
conversionServiceBuilder = new MultibinderBuilder<ConverterFactory>(binder(), ConverterFactory.class);
controllerModuleBuilder = new ControllerModuleBuilder();
exceptionResolverBuilder = new ExceptionResolverBuilder(binder());
paramProcessorBuilder = new MultibinderBuilder<ParamProcessorFactory>(binder(), ParamProcessorFactory.class);
viewScannerBuilder = new MultibinderBuilder<ViewScanner>(binder(), ViewScanner.class);
interceptorHandlersBuilder = new MultibinderBuilder<InterceptorHandler>(binder(), InterceptorHandler.class);
namedViewBudiler = new NamedViewBuilder(binder());
servlets = new LinkedList<HttpServlet>();
try {
//default registrations
bind(ViewResolver.class).to(DefaultViewResolver.class);
bind(ExceptionResolver.class)
.to(GuiceExceptionResolver.class);
bind(ExceptionHandler.class)
.annotatedWith(Names.named(ExceptionResolver.DEFAULT_EXCEPTIONHANDLER_NAME))
.to(DefaultExceptionHandler.class);
bind(ConversionService.class).asEagerSingleton();
registerConverter(new BooleanConverterFactory());
registerConverter(new DateConverterFactory());
registerConverter(new DoubleConverterFactory());
registerConverter(new LongConverterFactory());
registerConverter(new FloatConverterFactory());
registerConverter(new IntegerConverterFactory());
registerConverter(new StringConverterFactory());
bind(ParamProcessorsService.class);
registerParameterProc(HttpPostParam.Factory.class);
registerParameterProc(RequestScopedAttributeParam.Factory.class);
registerParameterProc(UriParam.Factory.class);
registerParameterProc(SessionAttributeParam.Factory.class);
registerParameterProc(ModelParam.Factory.class);
registerParameterProc(RequestParam.Factory.class);
registerParameterProc(ResponseParam.Factory.class);
registerParameterProc(HttpSessionParam.Factory.class);
registerParameterProc(InjectorParam.Factory.class);
bind(InterceptorService.class).asEagerSingleton();
bind(ViewScannerService.class);
registerViewScanner(NamedViewScanner.class);
registerViewScanner(RedirectViewScanner.class);
configureControllers();
//register MVC controllers
List<ServletDefinition> defs = controllerModuleBuilder.getControllerDefinitions();
if (defs.size() == 0) {
logger.log(Level.WARNING, "None controller has been defined in the MVC module");
}
for (ServletDefinition def : defs) {
String pattern = def.getUrlPattern();
HttpServlet servlet = def.createServlet(binder());
requestInjection(servlet);
serve(pattern).with(servlet);
servlets.add(servlet);
}
} finally {
exceptionResolverBuilder = null;
controllerModuleBuilder = null;
viewScannerBuilder = null;
paramProcessorBuilder = null;
namedViewBudiler = null;
conversionServiceBuilder = null;
interceptorHandlersBuilder = null;
}
}
/**
* This method can be used only for testing purpose
*/
public final List<HttpServlet> getServlets() {
return this.servlets;
}
/**
* Method bind to view's name some view.
*/
protected final NamedViewBindingBuilder bindViewName(String viewName) {
return this.namedViewBudiler.bindViewName(viewName);
}
/**
* The method registers a custom converter which converts strings to the
* concrete types. These converters are used for conversions from a HTTP request
* to the method's parameters.
*
* The all predefined default converters are placed in the 'converters' sub-package.
*/
protected final void registerConverter(ConverterFactory converterFactory) {
this.conversionServiceBuilder.registerInstance(converterFactory);
}
/**
* The method registers a custom converter which converts strings to the
* concrete types. These converters are used for conversions from a HTTP request
* to the method's parameters.
*
* The all predefined default convertors are placed in the 'converters' sub-package.
*/
protected final void registerConverter(Class<? extends ConverterFactory> convertorFactoryClazz) {
this.conversionServiceBuilder.registerClass(convertorFactoryClazz);
}
/**
* The method register into {@link ViewScannerService} a custom
* view scanner as a class
*
* @see ViewScannerService
*/
protected final void registerViewScanner(Class<? extends ViewScanner> scannerClass) {
this.viewScannerBuilder.registerClass(scannerClass);
}
/**
* The method register into {@link ViewScannerService} a custom
* view scanner as a instance.
*
* @see ViewScannerService
*/
protected final void registerViewScanner(ViewScanner scannerInstance) {
this.viewScannerBuilder.registerInstance(scannerInstance);
}
/**
* The method registers a custom parameter processor. The parameter processors
* converts/prepares/fills the values into invoked method's parameters.
* All predefined processors are placed in 'parameters' sub-package.
*
* @param paramProcFactory
*
* @see ParamProcessorFactory
* @see ParamProcessor
*/
protected final void registerParameterProc(Class<? extends ParamProcessorFactory> paramProcFactory) {
paramProcessorBuilder.registerClass(paramProcFactory);
}
/**
* Method registers a global interceptor class as a singleton. These global interceptors do pre/post
* processing for every request/response.
*
*
* @param interceptorHandler
*/
protected final void registerGlobalInterceptor(Class<? extends InterceptorHandler> interceptorHandlerClass) {
interceptorHandlersBuilder.registerClass(interceptorHandlerClass);
}
/**
* Method registers a global interceptor instance. These global interceptors do pre/post
* processing for every request/response.
*
* @param interceptorHandler
*/
protected final void registerGlobalInterceptorInstance(InterceptorHandler interceptorHandlerInstance) {
interceptorHandlersBuilder.registerInstance(interceptorHandlerInstance);
}
/**
* Method binds the exception handler to concrete exception type
* @param exceptionClazz
* @return
*/
protected final ExceptionResolverBindingBuilder bindException(Class<? extends Throwable> exceptionClazz) {
return this.exceptionResolverBuilder.bindException(exceptionClazz);
}
/**
* Method bind controller class to the concrete url.
* @param urlPattern
* @return
*/
protected final ControllerAndViewBindingBuilder control(String urlPattern) {
return this.controllerModuleBuilder.control(urlPattern);
}
public static interface ControllerBindingBuilder {
public ControllerBindingBuilder withController(Class<?> controller);
public ControllerBindingBuilder interceptor(Class<? extends InterceptorHandler> handlerClass);
}
public static interface ControllerAndViewBindingBuilder {
public ControllerBindingBuilder withController(Class<?> controller);
public void withView(String name);
public void withView(ViewPoint viewInstance);
}
public static interface ExceptionResolverBindingBuilder {
public void toHandler(Class<? extends ExceptionHandler> handlerClass);
public void toHandlerInstance(ExceptionHandler handler);
public void toErrorView(String viewName);
public void toErrorView(ViewPoint errorView);
}
public static interface NamedViewBindingBuilder {
public void toView(Class<? extends ViewPoint> viewCLass);
public void toViewInstance(ViewPoint view);
public void toJsp(String pathToFile);
}
}