How on earth are we supposed to create a Vaadin 6 application which is both managed by Guice and hosted on Google App Engine ? The solution is actually quite simple, but it can take a while to discover it.
The solution I present here also solves a common problem with Vaadin + GAE projects, which require different servlets to be deployed in production and development environments.
First, let me go through the steps I assume you have already completed :
OK, that's a lot of code. Let us review it chunk by chunk :
In the next piece of code, we first create a map of properties that we use to initialize the servlets.
Don't forget to bind the implementation of
By now, you might have realized something is wrong. Since the resolution of which implementation of
I hope you got it to work too, and please give some feedback in the comments if there is anything missing.
P.S.: If you don't know what to do next, just put an
The solution I present here also solves a common problem with Vaadin + GAE projects, which require different servlets to be deployed in production and development environments.
First, let me go through the steps I assume you have already completed :
- Setup Eclipse with GPE
- Created a GAE project
- Resolved all dependencies :
- vaadin.jar
- guice-3.0.jar
- guice-servlet-3.0.jar
- aopalliance.jar
- Successfully ran the application without Guice injection
(Note that I created the project with the GPE, and then added the vaadin JAR. I found it easier that way than creating the project with the Vaadin plugin)
If you have trouble with these steps, check around the web, there's already many resources that can help you. If still in trouble, post a comment and I'll describe those steps too.
So you have your Vaadin + GAE project, but you want to earn the benefits of Guice and Guice Servlet. In order to do that, you will have to let Guice handle the instanciation of your servlets. So let's configure your web.xml file with the Guice Servlet filter :
<filter> <filter-name>guiceFilter</filter-name> <filter-class>com.google.inject.servlet.GuiceFilter</filter-class> </filter> <filter-mapping> <filter-name>guiceFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <listener> <listener-class>com.yourcompany.yourproject.guice.Bootstrap</listener-class> </listener>
So far, nothing special, it's exactly what you can find on the Guice wiki. Go take a look if you're not familiar with it, it's worth its weight in gold :
http://code.google.com/p/google-guice/wiki/Servlets
In the web.xml, we reference a
http://code.google.com/p/google-guice/wiki/Servlets
In the web.xml, we reference a
Bootstrap
listener class. So let's code that class :public class Bootstrap extends GuiceServletContextListener { @Override protected Injector getInjector() { return Guice.createInjector(new MyServletModule(), new DomainModule()); } }
Again, nothing new here, just standard Guice Servlet config. But hold on, now is the time to have fun. Look at the code of our
ServletModule
public class MyServletModule extends ServletModule { @Override protected void configureServlets() { // Disable Vaadin debug mode in production if (GaeEnvironment.isProduction()) getServletContext().setAttribute("productionMode", "true"); // Set the application class name Map<String, String> params = new HashMap<String, String>(); params.put("application", MainApplication.class.getName()); // Deploy the servlet which is right for the current environment if (GaeEnvironment.isProduction()) { bind(ProdApplicationServlet.class).asEagerSingleton(); serve("/app/*", "/VAADIN/*").with(ProdApplicationServlet.class, params); } else { bind(DevApplicationServlet.class).asEagerSingleton(); serve("/app/*", "/VAADIN/*").with(DevApplicationServlet.class, params); } // Bind your Application implementation bind(Application.class).to(MainApplication.class).in(ServletScopes.SESSION); // OPTIONAL : In case you user Objectify 4 bind(ObjectifyFilter.class).in(Singleton.class); filter("/*").through(ObjectifyFilter.class); } }
OK, that's a lot of code. Let us review it chunk by chunk :
// Disable Vaadin debug mode in production if (GaeEnvironment.isProduction()) getServletContext().setAttribute("productionMode", "true");
As you might already be aware of, the debug feature of Vaadin is activated by default. This is not the kind of feature we want users to be able to use, so we want to make sure that it is automatically disabled when the application is in the cloud.
The
The
GaeEnvironment
class is only here to clarify the intent of the code, have a look at the implementation :
public class GaeEnvironment { public static boolean isProduction() { return SystemProperty.environment.value() == SystemProperty.Environment.Value.Production; } public static boolean isDevelopment() { return !isProduction(); } }
In the next piece of code, we first create a map of properties that we use to initialize the servlets.
// Set the application class name Map<String, String> params = new HashMap<String, String>(); params.put("application", MainApplication.class.getName()); // Deploy the servlet which is right for the current environment if (GaeEnvironment.isProduction()) { bind(ProdApplicationServlet.class).asEagerSingleton(); serve("/app/*", "/VAADIN/*").with(ProdApplicationServlet.class, params); } else { bind(DevApplicationServlet.class).asEagerSingleton(); serve("/app/*", "/VAADIN/*").with(DevApplicationServlet.class, params); }
And again, we check if we are in a Production or Local/Dev environment. The reason is that the GAE Production environment is different to an everyday servlet container, and Vaadin provides a specific implementation for it.
In the example above, our web application will be available at
Now let's look at the code of our
In the example above, our web application will be available at
http://localhost:8888/app/
and http://localhost:8888/VAADIN/
. You can get rid of the first one, which is only here to help you make nice looking URLs, but do NOT remove the second one, or you will break the integration.
Now let's look at the code of our
ProdApplicationServlet
:
@SuppressWarnings ("serial") @Singleton public class ProdApplicationServlet extends GAEApplicationServlet { private final Provider<Application> applicationProvider; @Inject public ProdApplicationServlet(Provider<Application> applicationProvider) { this.applicationProvider = applicationProvider; } @Override protected Application getNewApplication(HttpServletRequest request) throws ServletException { return applicationProvider.get(); } }
As you can see, this class is a simple extension of
The
GAEApplicationServlet
, which is provided by Vaadin. Since it does not support dependency injection, we have to subclass it and override its getNewApplication()
method so that it returns a Guice-managed instance of Application
. That way, our implementation of Application
can itself be injected.
The
DevApplicationServlet
is very similar, the only difference is that it extends a non-GAE-specific servlet.
@SuppressWarnings ("serial") @Singleton public class DevApplicationServlet extends ApplicationServlet { private final Provider<Application> applicationProvider; @Inject public DevApplicationServlet(Provider<Application> applicationProvider) { this.applicationProvider = applicationProvider; } @Override protected Application getNewApplication(HttpServletRequest request) throws ServletException { return applicationProvider.get(); } }
Don't forget to bind the implementation of
Application
you want to use :
// Bind your Application implementation bind(Application.class).to(MainApplication.class).in(ServletScopes.SESSION);
By now, you might have realized something is wrong. Since the resolution of which implementation of
Application
to instantiate is made by Guice, there shouldn't be any need to pass its name as an initialization parameter to the servlets. And you would be right, however the servlets follow a FAIL FAST strategy, and they need a valid Application
class name on initialization, even if it is different from the actual returned implementation.
I hope you got it to work too, and please give some feedback in the comments if there is anything missing.
P.S.: If you don't know what to do next, just put an
@Inject
annotation on your MainApplication
's constructor so that the parameters get injected.