A plugin for creating a
Vaadin applications on Grails.
Overview
Install:
grails install-plugin vaadin
After installing the plugin:
- Create a class in the
grails-app/vaadin directory that extends com.vaadin.Application .
- Specify this class in the Vaadin configuration file (
grails-app/conf/VaadinConfig.groovy ).
- Remove any Grails URL mappings for the URL where you want to access the Vaadin UI (in
grails-app/conf/UrlMappings.groovy )
- Run the application:
Usage
As of the plugin's 1.2 release, all Vaadin-related code should reside in the
grails-app/vaadin directory. This directory is a normal source directory, and should contain packages and Groovy classes as expected (e.g.
grails-app/vaadin/com/mycompany/vaadin/MyVaadinApplication.groovy ).
Placing the source in this directory allows the Grails environment to treat all Vaadin code as a special Grails artefact, enabling additional features, such as
- auto-reload after a source code change (just hit browser refresh)
- Spring dependency support, and
- integration with Grails internationalization (i18n).
Spring Dependencies
The traditional way in Grails to wire up Spring dependencies is to simply declare the dependency as a property in your source code, for example, an AuditService used to keep track of what happens in an application. Note the 'def auditService' property:
//Ok for normal Grails artefacts, but NOT IDEAL FOR VAADIN UI COMPONENTS:
class VaadinApplication extends com.vaadin.Application { def auditService init() {
auditService.log new ApplicationStartedEvent() //set up window and buttons, etc.
…
}
}However, this is
NOT recommended in your Grails-based Vaadin applications, for two reasons:
Reason 1: In order to support dependency injection across
all of your Vaadin components (beyond the initial Vaadin
com.vaadin.Application instance), you would be required to wire all of your Vaadin UI components in Spring, i.e.
grails-app/conf/spring/resources.groovy .
You wouldn't be able to call
Window window = new Window("My Window") in your code, and instead would need to acquire all instances from Spring, e.g.
Window window = applicationContext.getBean("myWindow"); (and don't forget to make all of those UI beans prototype scoped!).
For even relatively simple UIs, this could quickly become difficult to manage.
(Note: If you still want to do this to achieve a templating type of mechanism where the spring bean definitions act as a sort of UI template, this is ok - but you
must ensure that all beans have 'singleton = false' or scope = prototype'. Vaadin UI components cannot be application singletons - each user must get their own copy. Developer beware).
Reason 2: Perhaps the even more important reason requires understanding of Vaadin UIs: A Vaadin application runtime instance, and all of the objects that it reaches (children windows, buttons, etc) are bound to the HttpSession at runtime. Each user has their own Application instance, their own state. So what does this mean for Spring dependencies?
Spring beans in the runtime
ApplicationContext , such as all of your
*Service implementations (UserService, DocumentService, etc) are usually stateless - unlike the Vaadin components. All Vaadin components that used traditional dependency injection ('def userService', etc) would retain a strong reference to each Service or any other bean in the
ApplicationContext .
Why is this a problem? Session objects (and their contents, such as the Vaadin application and all of its children) might need to be serialized and paged to disk; the servlet container is free to store sessions in memory and then offload them to disk if memory becomes full. If you have a larger Vaadin application and thousands of users, the servlet container might need to do this. The same serialization could also happen if you use clustered sessions, or an enterprise caching framework that serializes Session data across a cluster.
If your Vaadin components retain strong references to stateless Spring beans that are application singletons, each serialized Session would also serialize the Spring beans! This makes the serialized object graph larger than it needs to be. Also, when deserialized, these beans would not be under Spring's control and would very likely fail to work.
Using Dependencies
So, based on the above reasons why it may not be ideal to use standard dependency injection, the Vaadin plugin adds two dynamic methods to all classes in
grails-app/vaadin to support dependency acquisition:
getBean(String beanName)
getBean(Class beanType)
You can access any Spring bean as necessary by calling this method in any class under
grails-app/vaadin . Here is the above Application example refactored to use this mechanism:
//Cleanly serializable implementation:
class VaadinApplication extends com.vaadin.Application { init() {
getBean(AuditService).log new ApplicationStartedEvent() //set up window and buttons, etc.
…
}
}The above example uses the
getBean(Class clazz) method to acquire the dependency by type. We could have just as easily looked up the bean by name:
getBean("defaultAuditService") . These methods will always ask the Spring ApplicationContext for the dependency. Spring caches these references of course, so the call is extremely fast, but still serialization-safe.
(Aside: the 'getBean' approach is one way to solve the serialization problem. Another way is to dynamically replace all property definitions, e.g. 'def userService', with a runtime proxy: the proxy can be serialized safely, but when deserialized it would know how to look up the 'real' target bean from the Spring
ApplicationContext at runtime. The current version of the Vaadin plugin does not support this dynamic proxy approach - please discuss on the dev list if you'd like to help make this happen!).
Internationalization (i18n).
Internationalization (i18n) is one of the things you have to do yourself when writing standard Vaadin applications - messing with Resource Bundles, MessageFormats, etc. But you don't have to worry about that when using the Grails Vaadin plugin!
All classes under
grails-app/vaadin automatically receive dynamically added methods to 'hook' in to Grails I18N support:
i18n(String key, Collection args=null, Locale locale=LocaleContextHolder.getLocale())
i18n(String key, String defaultMessage, Collection args=null, Locale locale=LocaleContextHolder.getLocale())
The first method will throw an exception if there is no matching key. The second one will show the default message if the key doesn't exist.
All message keys should be defined in the standard Grails i18n location in
grails-app/i18n/messages*.properties .
As you can see, the
args and
locale arguments are optional. so you can call the method in many permutations. If you don't specify a Locale (pretty common), it defaults to the one Grails sets up automatically for a request as defined in the Grails
chapter on internationalization .
Some examples:
Window window = new Window(i18n("default.home.label"));//button with label arguments:, e.g. "You have clicked {0} times since 2:15 pm!":
Button button = new Button(i18n("button.click", [numClicked, sinceDate]));window.addComponent button..Upgrading from 1.0 to 1.1
If you're upgrading from the 1.0 Vaading plugin, your old Vaadin configuration will not be automatically upgraded. You will want to use the following as a starting point in your
grails-app/conf/VaadinConfig.groovy file:
vaadin { //Your Vaadin application class that extends com.vaadin.Application:
applicationClass = "com.mycompany.MyVaadinApplication" //the context relative path where you want to access your Vaadin UI. Default to the context root.
contextRelativePath = "/" productionMode = false googleAppEngineMode = false
}environments {
production {
vaadin {
productionMode = true
}
}
}Release Notes
- 1.2 - Created new 'vaadin' artefact type - all Vaadin code should now reside in
grails-app/vaadin . New features: Seamless integration with Grails Internationalization (i18n) as well as Spring bean lookup from any Vaadin component.
- 1.1.2 - Upgraded to the latest stable Vaadin release (6.3.4), fixing these issues.
- 1.1.1 - A minor point release that ensures compatibility with Grails 1.1 and higher. However, Groovy 1.7 language features (anonymous inner classes, etc) are only available in Grails 1.3+ environments (Groovy 1.7 is part of Grails 1.3+).
- 1.1 - Latest stable Vaadin release (6.3.3). Ability to specify a path to your Vaadin UI other than the root context path (e.g. http://localhost:8080/myapp/vaadinUI). Environment-specific configuration support (production, development, etc. Custom environments too 'beta', 'uat', etc).