Vous êtes sur la page 1sur 43

Spring Rich Client

A practical introduction
Lieven DOCLO

An introduction to the Spring Rich Client framework

CONTENTS
Introduction ........................................................................................................................................................................................ 5 Overview ......................................................................................................................................................................................... 5 Getting our hands dirty ................................................................................................................................................................... 5 The Spring Rich Client framework, a quick dissection ........................................................................................................................ 8 Anatomy of a rich client application ............................................................................................................................................... 8 Application windows ....................................................................................................................................................................... 8 Views ............................................................................................................................................................................................... 9 Commands ...................................................................................................................................................................................... 9 Messages and icons ...................................................................................................................................................................... 11 Other components ........................................................................................................................................................................ 11 Applications ...................................................................................................................................................................................... 12 Application lifecycle ...................................................................................................................................................................... 12 Hooking into a lifecycle ................................................................................................................................................................. 12 Application .................................................................................................................................................................................... 12 Example: Adding login functionality to the application ................................................................................................................ 12 Other possible uses ....................................................................................................................................................................... 13 Playing with the statusbar ............................................................................................................................................................ 13 Elaborate example: Adding multiple toolbars to the screen ........................................................................................................ 14 Commands ........................................................................................................................................................................................ 17 Why the abstraction ..................................................................................................................................................................... 17 How to create a simple command ................................................................................................................................................ 17 Configure a programmatically created command ........................................................................................................................ 17 Changing the command to a visual component ........................................................................................................................... 17 Command internationalization and images .................................................................................................................................. 18 Grouping commands ..................................................................................................................................................................... 18 Building a command group in a Spring context ............................................................................................................................ 18 Getting a Spring configured command in code ............................................................................................................................ 19 Views ................................................................................................................................................................................................. 20 What is a view ............................................................................................................................................................................... 20 View descriptors ........................................................................................................................................................................... 20 Creating views ............................................................................................................................................................................... 20 2

Creating a view descriptor for a view ........................................................................................................................................... 21 Showing the view in the application ............................................................................................................................................. 21 Binding and forms ............................................................................................................................................................................. 22 Formmodels and valuemodels ...................................................................................................................................................... 22 What is a valuemodel ............................................................................................................................................................... 22 What is a formmodel ................................................................................................................................................................ 22 The default formmodel ............................................................................................................................................................. 22 Buffering ................................................................................................................................................................................... 22 Read-only manipulation ............................................................................................................................................................ 23 Validation .................................................................................................................................................................................. 23 Creating formmodels ................................................................................................................................................................ 23 Binding .......................................................................................................................................................................................... 24 What is binding ......................................................................................................................................................................... 24 How does binding work and what does it do ........................................................................................................................... 24 Binders ...................................................................................................................................................................................... 24 Binder examples........................................................................................................................................................................ 24 Creating your own binder ......................................................................................................................................................... 24 Forms ............................................................................................................................................................................................ 26 What is a form........................................................................................................................................................................... 26 Creating a form ......................................................................................................................................................................... 26 Form builders ............................................................................................................................................................................ 28 Binder selection ........................................................................................................................................................................ 28 Internationalization................................................................................................................................................................... 29 Adding forms to forms, aka child forms .................................................................................................................................... 29 Form validation ......................................................................................................................................................................... 29 Form component interceptors ...................................................................................................................................................... 30 Introduction .............................................................................................................................................................................. 30 Creating your own interceptor ................................................................................................................................................. 30 Configuration ............................................................................................................................................................................ 30 Built-in interceptors .................................................................................................................................................................. 31 Validation .......................................................................................................................................................................................... 34 Why validation .............................................................................................................................................................................. 34 3

Validation choices ......................................................................................................................................................................... 34 Rule validation .............................................................................................................................................................................. 34 Using the rule framework ......................................................................................................................................................... 34 Constraints ................................................................................................................................................................................ 34 Validation triggers ..................................................................................................................................................................... 35 Dependent properties ............................................................................................................................................................... 35 Hibernate validator integration .................................................................................................................................................... 35 Valang validation framework integration ..................................................................................................................................... 35 integrating your own validation framework ................................................................................................................................. 35 Exception handling............................................................................................................................................................................ 36 Avoiding try-catch constructs ....................................................................................................................................................... 36 Registering an exception handler ................................................................................................................................................. 36 Build-in exception handlers .......................................................................................................................................................... 37 Log silently ................................................................................................................................................................................ 37 Show a message dialog ............................................................................................................................................................. 37 Hibernate validation exception handling .................................................................................................................................. 38 Custom exception handlers ...................................................................................................................................................... 38 Using the right exception handler for the right exception ........................................................................................................... 38 Simple delegation ..................................................................................................................................................................... 39 Unwrapping exceptions ............................................................................................................................................................ 39 Security ............................................................................................................................................................................................. 41 Integrating security in a GUI application....................................................................................................................................... 41 Spring Security integration ........................................................................................................................................................... 41 Turning on security awareness of the GUI .................................................................................................................................... 41 Securing commands ...................................................................................................................................................................... 42 Wizards ............................................................................................................................................................................................. 43 Why use wizards ........................................................................................................................................................................... 43 Creating a wizard .......................................................................................................................................................................... 43 Showing a wizard .......................................................................................................................................................................... 43

INTRODUCTION OVERVIEW
Rich clients are becoming increasingly more popular nowadays. A quick search through Google on rich client framework java return over 200.000 results. These frameworks can be divided into 2 distinct categories: The Swing or SWT based frameworks, designed for out-of-browser applications, to be used with technologies like Java Webstart. The Rich Internet Application (RIA) frameworks, designed for providing a rich client experience within the confines of a web browser (such as Mozilla or Internet Explorer). Today, RIA frameworks make up the bulk of the rich client frameworks on the market today. Among these youll find names like Flex, Google Web Toolkit, Ice Faces, Their popularity is mainly caused by the ease of deployment: no installation is needed, almost every user already has a Web browser. For Swing (or SWT) based application, the landscape is a bit different. There are only a handful of complete frameworks on the market: Eclipse RCP, the platform on which the Eclipse IDE has been built Netbeans RCP, the platform on which the Netbeans IDE has been built These frameworks are complete platforms, which have proven themselves through their respective IDEs to showcase their possibilities. However, these possibilities come at a steep cost. With Eclipse RCP, for example, youll be straying of the known Swing path and enter the world of SWT, and both dont play well together. With Netbeans RCP, youre mostly confined to the Netbeans IDE to do your development (that is, if you want to develop quickly). Both platforms can be quite cumbersome and come with a complete baggage package bundled. Its complexity can be overwhelming for a standard Swing developer and does not always promote effective, good programming style. The Spring Rich Client framework does not promote itself as a complete platform. Instead, it provides developer a clear and easy way to build enterprise-class applications, without straying too far from the standard, well-known path, whilst ensuring enough flexibility to cope with the difficult issues coupled to the development of enterprise applications. Built on the strong foundations of the Spring Framework, it combines the best practices advocated by the Spring Framework with an component-based abstraction on top of Swing to ease development of Swing rich client applications. This introduction will try to cover as much ground as possible to let you hit the ground running when starting with a Spring Rich Client application.

GETTING OUR HANDS DIRTY


Well start off the introduction with a concise example of a Spring Rich Client application. As with all Spring applications, Spring beans need to be configured within the container. Currently in Spring Rich Client, only XML based configuration has been tested, although with the new Spring 2.5 release and its component-scanning abilities, it should be possible to narrow your configuration down to the essentials. This is something that will possibly be improved in future releases.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/springbeans-2.0.xsd">

<bean id="application" class="org.springframework.richclient.application.Application">

<constructor-arg index="0" ref="applicationDescriptor" /> <constructor-arg index="1" ref="lifecycleAdvisor" /> </bean> <bean id="lifecycleAdvisor" class="org.springframework.richclient.samples.simple.app.SimpleLifecycleAdvisor"> <property name="windowCommandBarDefinitions" value="org/springframework/richclient/samples/simple/ui/commands-context.xml" /> <property name="startingPageId" value="initialView" /> <property name="windowCommandManagerBeanName" value="windowCommandManager" /> <property name="menubarBeanName" value="menuBar" /> <property name="toolbarBeanName" value="toolBar" /> </bean> <bean id="initialView" class="org.springframework.richclient.application.support.DefaultViewDescriptor"> <property name="viewClass" value="org.springframework.richclient.samples.simple.ui.InitialView" /> <property name="viewProperties"> <map> <entry key="firstMessage" value="firstMessage.text" /> <entry key="descriptionTextPath" value="org/springframework/richclient/samples/simple/ui/initialViewText.html" /> </map> </property> </bean> <bean id="serviceLocator" class="org.springframework.richclient.application.ApplicationServicesLocator"> <property name="applicationServices" ref="applicationServices" /> </bean> <bean id="applicationServices" class="org.springframework.richclient.application.support.DefaultApplicationServices" /> <bean id="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster" /> <bean id="applicationDescriptor" class="org.springframework.richclient.application.support.DefaultApplicationDescriptor"> <property name="version" value="1.0" /> </bean> <bean id="applicationObjectConfigurer" depends-on="serviceLocator" class="org.springframework.richclient.application.config.DefaultApplicationObjectConfigurer"> </bean> <bean id="lookAndFeelConfigurer" class="org.springframework.richclient.application.config.JGoodiesLooksConfigurer"> <property name="popupDropShadowEnabled" value="false" /> <property name="theme"> <bean class="com.jgoodies.looks.plastic.theme.ExperienceBlue" /> </property> </bean> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basenames"> <list> <value>org.springframework.richclient.samples.simple.ui.messages</value> <value>org.springframework.richclient.application.messages</value> </list> </property> </bean> <bean id="imageResourcesFactory" class="org.springframework.context.support.ResourceMapFactoryBean"> <property name="locations"> <list> <value>classpath:org/springframework/richclient/image/images.properties</value> <value>classpath:org/springframework/richclient/samples/simple/ui/images.properties</value> </list> </property> </bean>

<bean id="imageSource" class="org.springframework.richclient.image.DefaultImageSource"> <constructor-arg index="0" ref="imageResourcesFactory" /> <property name="brokenImageIndicator" Value="/org/springframework/richclient/images/alert/error_obj.gif" /> </bean> <bean id="formComponentInterceptorFactory" class="org.springframework.richclient.form.builder.support.ChainedInterceptorFactory"> <property name="interceptorFactories"> <list> <bean class="org.springframework.richclient.form.builder.support.ColorValidationInterceptorFactory"> <property name="errorColor" value="255,245,245" /> </bean> <bean class="org.springframework.richclient.form.builder.support.OverlayValidationInterceptorFactory" /> <bean class="org.springframework.richclient.text.TextComponentPopupInterceptorFactory" /> <bean class="org.springframework.richclient.list.ComboBoxAutoCompletionInterceptorFactory" /> </list> </property> </bean> <bean id="rulesSource" class="org.springframework.richclient.samples.simple.domain.SimpleValidationRulesSource" /> <bean id="conversionService" class="org.springframework.richclient.application.DefaultConversionServiceFactoryBean"> <property name="formatterFactory"> <bean class="org.springframework.richclient.samples.simple.ui.SimpleAppFormatterFactory" /> </property> </bean> </beans>

This sample is in fact on of the Spring Rich Client samples included in the sources, some minor adjustments not taken into account. When running the simple project, the output will look something like this:

THE SPRING RICH CLIENT FRAMEWORK, A QUICK DISSECTION ANATOMY OF A RICH CLIENT APPLICATION
A Swing rich client in its basic form is quite simple:

Navigation

Content

Status bar An application consists of a application windows, which in most cases contains: The main content pane One or more navigational structures, such as a menu bar or a toolbar A statusbar to show all sorts of messages to the user These can be augmented with global search fields, help functions or other components that an application might require.

APPLICATION WINDOWS
An application window is the foundation on which every rich client is built. Without an application window, you dont have a place to put the components that make up your screens. In Spring Rich Client, the application window is represented by the ApplicationWindow class. The application window builds the visual window component and shows it to the user. Application windows are not created individually in Spring Rich Client. A factory pattern, called ApplicationWindowFactory handles instantiation of an application window. This way windows are created in a clear and uniform way throughout the application, without bothering the developer. In our hands-dirty example, you will not find this factory. Spring Rich Client does not need to have a ApplicationWindowFactory defined. If it is not configured, Spring Rich Client will fall back on a default implementation within the framework, consisting of a simple view area with a menubar, statusbar and toolbar. You can, however, define your own application factory. To do this, you just need to implement an ApplicationWindowFactory and define a bean in your application context with your new class. Spring Rich Client will pick up the class and use it instead of the default implementation. The above mechanism, also known as service location, is widely used throughout Spring Rich Client, to facilitate configuration for the developers.

You can for example create an application window factory that leaves out the toolbar, but creates a outlook-like bar or taskpane oriented navigation on the left side instead.

VIEWS
Application windows are containers for views. A view can be seen as an individual window representing a specific state within the application. Views are the main view area of your applications and will contain the bulk of the user interaction, unless youve chosen for a dialog-based approach. You can for example have a flow of views representing a process within your application, or a view containing a table which, when double-clicked, shows a detail of the selected item. In our first example, a view shows a HTML file together with a message from a resource bundle. Implementing a view is quite simple. When viewing the example view, youll see this:
public class InitialView extends AbstractView { // omitted for brevity... /** * Create the actual UI control for this view. It will be placed into the window according to the layout of * the page holding this view. */ protected JComponent createControl() { // In this view, we're just going to use standard Swing to place a // few controls. // The location of the text to display has been set as a Resource in the // property descriptionTextPath. So, use that resource to obtain a URL // and set that as the page for the text pane. JTextPane textPane = new JTextPane(); JScrollPane spDescription = getComponentFactory().createScrollPane(textPane); try { textPane.setPage(getDescriptionTextPath().getURL()); } catch (IOException e) { throw new RuntimeException("Unable to load description URL", e); } JLabel lblMessage = getComponentFactory().createLabel(getFirstMessage()); lblMessage.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0)); JPanel panel = getComponentFactory().createPanel(new BorderLayout()); panel.add(spDescription); panel.add(lblMessage, BorderLayout.SOUTH); return panel; } }

AbstractView mandates that you implement the createControl method. This method can deliver any JComponent to show its contents. In most cases, this will be some sort of container or panel.

COMMANDS
The entire menu bar system (and derived navigational structures) are command based. In essence, this means youll never make a JMenu or JMenuItem manually again. Ever. Simply put, youll create a command, which contains code that needs to be executed (for example, change a view or print the current selected item). Spring Rich Client will handle the creation of the visual components and couple the command behavior to the visual components behavior.

In Spring Rich Client, the command context is a separate context that needs to be defined in the main application context lifecycle. In our example, the command context looks like this:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/springbeans-2.0.xsd"> <bean id="windowCommandManager" class="org.springframework.richclient.application.support.ApplicationWindowCommandManager"> </bean> <bean id="menuBar" class="org.springframework.richclient.command.CommandGroupFactoryBean"> <property name="members"> <list> <ref bean="fileMenu" /> <ref bean="windowMenu" /> <ref bean="helpMenu" /> </list> </property> </bean> <bean id="toolBar" class="org.springframework.richclient.command.CommandGroupFactoryBean"> <property name="members"> <list/> </property> </bean> <bean id="fileMenu" class="org.springframework.richclient.command.CommandGroupFactoryBean"> <property name="members"> <list> <bean class="org.springframework.richclient.command.support.ExitCommand" /> </list> </property> </bean> <bean id="windowMenu" class="org.springframework.richclient.command.CommandGroupFactoryBean"> <property name="members"> <list> <bean class="org.springframework.richclient.command.support.NewWindowCommand" /> <value>separator</value> <bean class="org.springframework.richclient.command.support.ShowViewMenu" /> </list> </property> </bean> <bean id="helpMenu" class="org.springframework.richclient.command.CommandGroupFactoryBean"> <property name="members"> <list> <ref bean="helpContentsCommand" /> <value>separator</value> <ref bean="aboutCommand" /> </list> </property> </bean> <bean id="helpContentsCommand" class="org.springframework.richclient.command.support.HelpContentsCommand"> <property name="helpSetPath" value="help/simple.hs" /> </bean> <bean id="aboutCommand" class="org.springframework.richclient.command.support.AboutCommand" /> </beans>

As you can see, no Swing-like components are being used to create the menu.

10

MESSAGES AND ICONS


Internationalizing your application can be quite an important part of your work, and sometimes also one of the most timeconsuming too. Spring Rich Client supports internationalization through resource bundles defined in the application context (see the messageSource bean). For example, Spring Rich Client provides a mechanism to set the title of your application through the application descriptor. If you set the key applicationDescriptor.title to some value, that value will show up as the title of your application. Messages are found through MessageSource implementation, for which the resource bundle implementation is perhaps the most widely used. The same is true for application icons and images in general. The key applicationDescriptor.icon sets the applications icon. Like messages, the images are found through a ImageSource, which contains property files with the keys and the images that go with the keys, and the location where the images can be found (in the jar for example, or somewhere else).

OTHER COMPONENTS
Spring Rich Client also contains other components to make rich client programming easier. A form framework that handles easy form creation, undo functionality and validation. Exception handling to show visually attractive messages when your application goes down the drain. A wizard framework to make data input for the not-so-tech-savvy users simpler and much more. Well discuss these into detail later.

11

APPLICATIONS APPLICATION LIFECYCLE


The lifecycle in a Spring Rich Client application is what it says it is. Its the behavior of the application through the time when it is running. What the application should do at startup, what it needs to show, how it should behave when its being closed these are all aspects of the application lifecycle. In Spring Rich Client, an ApplicationLifecycleAdvisor manages the lifecycle of the application. In our example, the lifecycle advisor handles the creation of the command context, what view should be showed initially, what command bean should be used for the menu, etc

HOOKING INTO A LIFECYCLE


In the application lifecycle you can intervene on points within startup, shutdown or other events such as opening a window. These hooks are present in the ApplicationLifecycleAdvisor and can for example be used to ask data needed before startup. You could also stop an application from exiting (by asking a confirmation for example).

APPLICATION
An application in Spring Rich Client is comprised of 2 things: a lifecycle advisor (how it should behave) and a descriptor (what it should do). The Application class is also the class that starts the actual application: it calls the startup hooks, and shows the initial window and view.

EXAMPLE: ADDING LOGIN FUNCTIONALITY TO THE APPLICATION


Logging into an application is quite common behavior. Youll probably want to show the login dialog before showing your application window (you might want to personalize the application window based on who has logged in). In Spring Rich Client, this means after the commands have been created (which is one step before the window gets created and showed). In this example, well let a imaginary LoginHandler class check whether we can login or not. For the record, basic login functionality has been included into Spring Rich Client, but this would bring us out of scope for this example. Well discuss the security features later. Assuming our handler will show a login screen and handle the login logic, we only need to hook it into the application lifecycle advisor:
public class LoginLifecycleAdvisor extends DefaultApplicationLifecycleAdvisor { private LoginHandler handler; public void setHandler(final LoginHandler handler) { this.handler = handler; } public void onCommandsCreated(final ApplicationWindow window) { super.onCommandsCreated(window); handler.doLogin(); } }

12

As you can see, this is quite easy. Off course youd want to know when a user is already logged in and just creates a new application window (which also triggers this method), so it doesnt show this login window again, something like holding a security context within your application.

OTHER POSSIBLE USES


Lifecycle advisor subclassing can also be handy in other aspects. Any Spring Rich Client application knows which lifecycle advisor is used for its execution. You can ask this by calling
Application.instance().getLifecycleAdvisor()

You could for example create an application window factory that delivers different application window applications based on which lifecycle advisor was used:
public class DefaultApplicationWindowFactory implements ApplicationWindowFactory { private static final Log logger = LogFactory.getLog(DefaultApplicationWindowFactory.class); public ApplicationWindow createApplicationWindow() { ApplicationLifecycleAdvisor lifecycleAdvisor = Application.instance().getLifecycleAdvisor(); if (lifecycleAdvisor instanceof OutlookNavigatorApplicationLifecycleAdvisor) { return OutlookNavigatorApplicationWindowFactory.create(); } else if (lifecycleAdvisor instanceof TaskPaneNavigatorApplicationLifecycleAdvisor) { return TaskPaneNavigatorApplicationWindowFactory.create(); } return new DefaultApplicationWindow(); } static class TaskPaneNavigatorApplicationWindowFactory { public static ApplicationWindow create(boolean onlyOneExpanded) { ... } } static class OutlookNavigatorApplicationWindowFactory { public static ApplicationWindow create() { ... } } }

PLAYING WITH THE STATUSBAR


Changing the status bar can be done by calling the statusbar through the lifecycle advisor:
Application.instance().getLifecycleAdvisor().getStatusBar();

Again, if youre creating your own lifecycle advisor, youre able to override this method and perhaps supply your own status bar implementation. The standard status bar supports: Displaying messages, normal messages as well as errors Containing a progress monitor to track long-running processing (and cancelling them) Your status bar may hold a clock, the current logged in user, the connected server, 13

ELABORATE EXAMPLE: ADDING MULTIPLE TOOLBARS TO THE SCREEN


In this example well add multiple toolbar support in Spring Rich Client. Management of the standard toolbar is done in three places: The application lifecycle advisor manages the bean name of the toolbar The application window puts the actual toolbar on screen The application window configurer manages the visibility of the toolbar For this example, Ill create the first two (and an factory for the application window). First, were going to extends the default lifecycle advisor with the ability to enter multiple toolbars, while maintaining backwards compatibility.
public class CustomApplicationLifecycleAdvisor extends DefaultApplicationLifecycleAdvisor { private List toolBarBeanNames; public void setToolBarBeanNames(List toolBarBeanNames) { this.toolBarBeanNames = toolBarBeanNames; } public void setToolbarBeanName(String toolbarBeanName) { toolBarBeanNames = new ArrayList(); toolBarBeanNames.add(toolbarBeanName); } public CommandGroup[] getToolBarCommandGroups() { if(toolBarBeanNames == null || toolBarBeanNames.size() == 0) { return new CommandGroup[] { new CommandGroup() }; } else { CommandGroup[] groups = new CommandGroup[toolBarBeanNames.size()]; for (int i = 0; i < toolBarBeanNames.size(); i++) { groups[i] = getCommandGroup(toolBarBeanNames.get(i).toString()); } return groups; } } }

Next, were going to create an application window that is able to show the multiple toolbars
public class MultipleToolbarApplicationWindow extends DefaultApplicationWindow { private CommandGroup[] toolBarCommandGroups; protected void init() { super.init(); if(getAdvisor() instanceof CustomApplicationLifecycleAdvisor) { this.toolBarCommandGroups = ((CustomApplicationLifecycleAdvisor) getAdvisor()).getToolBarCommandGroups(); } else { this.toolBarCommandGroups = new CommandGroup[] {getAdvisor().getToolBarCommandGroup()}; } } protected JComponent createToolBarControl() {

14

JPanel panel = new JPanel(); panel.setLayout(new GridLayout(toolBarCommandGroups.length, 1)); for (int i = 0; i < toolBarCommandGroups.length; i++) { CommandGroup toolBarCommandGroup = toolBarCommandGroups[i]; JComponent toolBar = toolBarCommandGroup.createToolBar(); toolBarCommandGroup.setVisible( getWindowConfigurer().getShowToolBar() ); panel.add(toolBar); } return panel; }

}Finally, a factory to create the new application window


public class MultipleToolbarApplicationWindowFactory implements ApplicationWindowFactory { public ApplicationWindow createApplicationWindow() { return new MultipleToolbarApplicationWindow(); } }

Now, to configure the multiple toolbars, here are the beans you need to configure. First the application lifecycle in the main application context
<bean id="lifecycleAdvisor" class="org.springframework.richclient.test.CustomApplicationLifecycleAdvisor"> <property name="windowCommandBarDefinitions" value="ui/commands-context.xml" /> <property name="startingPageId" value="initialView" /> <property name="windowCommandManagerBeanName" value="windowCommandManager" /> <property name="menubarBeanName" value="menuBar" /> <property name="toolBarBeanNames"> <list> <value>toolBar</value> <value>toolBarCopy</value> </list> </property> </bean>

You also need to define the application window factory in your context, so itll use that one.
<bean id="appWindowFactory" class="org.springframework.richclient.test.MultipleToolbarApplicationWindowFactory"/>

And then the toolbars in the command context


<bean id="toolBar" class="org.springframework.richclient.command.CommandGroupFactoryBean"> <property name="members"> <list> <value>newCommand</value> <value>saveCommand</value> <value>printCommand</value> <value>separator</value> <value>propertiesCommand</value> </list> </property> </bean> <bean id="toolBarCopy" class="org.springframework.richclient.command.CommandGroupFactoryBean"> <property name="members"> <list> <value>newCommand</value> <value>saveCommand</value> <value>printCommand</value> <value>separator</value> <value>propertiesCommand</value> </list> </property> </bean>

15

Thats all there is to it. Ive started from a project created with the maven archetype, so you can try it out if you want. The result will be something like this

16

COMMANDS WHY THE ABSTRACTION


Creating an abstraction over an action-based operation is quite logical when you think about it. You have behavior, which can be visually represented in different ways in Swing: a menu, a button or even just a clickable label. Managing this behavior can be cumbersome if you need to maintain it, especially when certain behavior is to be represented many times and in different ways.

HOW TO CREATE A SIMPLE COMMAND


Lets make a simple command that shows a messagebox when executed:
public class MessageBoxCommand extends ActionCommand { protected void doExecuteCommand() { JOptionPane.showMessageDialog(Application.instance().getActiveWindow().getControl(), "Hello world!"); } }

Creating a new command can be done by extending ActionCommand. This class mandates that you implement the doExecuteCommand method, which represents the behavior of the command.

CONFIGURE A PROGRAMMATICALLY CREATED COMMAND


Programmatically created commands need to be configured, since Spring Rich Client heavily relies on the Spring container to do the configuring. So when you create command objects in code, youll have to do the configuring yourself. This can be done by calling the command configurer through the service locator in Spring Rich Client:
MessageBoxCommand command = new MessageBoxCommand(); commandConfigurer = (CommandConfigurer) ApplicationServicesLocator.services().getService( CommandConfigurer.class); commandConfigurer.configure(command);

After the command has been configured, it can be used to create components.

CHANGING THE COMMAND TO A VISUAL COMPONENT


After a command is configured, it can be used to create visual components. These can be: Menu items Buttons Creating a visual component from a command is easy. Every command has methods to create visual components, with the commands behavior coupled to it. For buttons this is
command.createButton(...);

For menu items this is


command.createMenuItem(...);

Both methods have various parameter configurations, for more information on which to use in your scenario I refer to the JavaDoc documentation.

17

COMMAND INTERNATIONALIZATION AND IMAGES


A button needs a text label, perhaps an icon. Well now show you how you can put messages on your commands and link icons to them. When configuring a command, Spring Rich Client sets a face descriptor on the command. This face descriptor manages how the command looks like. A face descriptor is put on the command by utilizing the id of the command. In a Spring configured command, this is the id of the Spring bean. Programmatically created commands need to set their command ids themselves. If you dont set the commands id, Spring Rich Client will fallback to the camel-cased class name of the command class. In our example, this will be the case, as we havent set the id. The label of the command, and other text related messages for that matter, is found in the message source configured for the Spring Rich Client application. To find the label of a command, Spring Rich client searches for:
[face descriptor id].label = The label of the command

Similarly, you can put a caption on a command. In the case of a JButton, this will translate itself into a tooltip message:
[face descriptor id].caption = The caption of the command

Commands can also have icons coupled to them. Spring Rich Client will look for the message keys and images in the image source configured for the application. To find the icon for a command, Spring Rich client will search for:
[face descriptor id].icon = some_icon.png

GROUPING COMMANDS
Commands can also be groups. Command groups in Spring Rich Clients are composite commands. They can be used to create menus, button stacks and other aggregate components. Command groups behave similarly to commands (command groups are subclasses of commands), so the configuration works in the same way. They can also be configured in the Spring context, as well as programmatically. However, in the latter case, the same rules as commands apply. Adding commands to a command group is easy, just call the add method. Creating a visual component of a command group works the same way as commands. Spring Rich Client provides functionality for: Menus Vertical button stacks Horizontal button bars Toolbars Popup menus See the JavaDoc documentation for more information on the usage of these creation methods. With some effort, you can even make trees, taskpanes or outlook-bar style grouping.

BUILDING A COMMAND GROUP IN A SPRING CONTEXT


Given our hands-dirty example, well look at a command group configured there:
<bean id="windowMenu" class="org.springframework.richclient.command.CommandGroupFactoryBean"> <property name="members"> <list> <bean class="org.springframework.richclient.command.support.NewWindowCommand" /> <value>separator</value>

18

<bean class="org.springframework.richclient.command.support.ShowViewMenu" /> </list> </property> </bean>

As you can see, creating a command group is straightforward. You create a command group through a factory bean and set the members. You may have noticed the separator value in the members. Spring Rich Client has facilitated adding separators to menus and toolbars by adding this as a shortcut. Other shortcuts that can be used as values are: glue Add a glue between commands command:xyz Add a command with id xyz to the group. You could use this instead of bean references group:abc Add a command group with id abc to the group. You could use this instead of bean references Not only commands or shortcuts can be added. Also just ordinary JComponent can be added to command groups. This way you can easily add a textfield to a toolbar. These command groups can then be referenced for example in the lifecycle advisor to be used as the applications menu bar, tool bar or other navigation you might have implemented.

GETTING A SPRING CONFIGURED COMMAND IN CODE


Getting a command that you have configured in a Spring context can be done by searching for it based on its id:
Application.instance().getActiveWindow().getCommandManager().getCommand(commandId);

This will return the command if it can find a command with that id, or null otherwise. You dont need to configure this command after youve found it, Spring Rich Client has already handled this for you.

19

VIEWS WHAT IS A VIEW


A view is a visual representation of concepts within your application. Everything you show in the main application window is contained within a view. There can be multiple views on an application page, but only one view is visible at a time. View instances encapsulate the creation of and access to the visual presentation of the underlying control. A view's descriptor, which is effectively a singleton, can be asked to instantiate new instances of a single view for display within an application with multiple windows. In other words, a single view instance is never shared between windows.

VIEW DESCRIPTORS
Every view has a view descriptor. Its metadata about a view; a view descriptor is effectively a singleton view definition. A descriptor also acts as a factory which produces new instances of a given view when requested, typically by a requesting application page. A view descriptor can also produce a command which launches a view for display on the page within the current active window. View descriptors produce the page components (in this case views) that will be shown to the users.

CREATING VIEWS
Creating a new view is done through subclassing the AbstractView class. This class mandates you to implement one method: createControl. In our example, the initial view class looks like this:
public class InitialView extends AbstractView { // omitted for brevity protected JComponent createControl() { // In this view, we're just going to use standard Swing to place a // few controls. // The location of the text to display has been set as a Resource in the // property descriptionTextPath. So, use that resource to obtain a URL // and set that as the page for the text pane. JTextPane textPane = new JTextPane(); JScrollPane spDescription = getComponentFactory().createScrollPane(textPane); try { textPane.setPage(getDescriptionTextPath().getURL()); } catch (IOException e) { throw new RuntimeException("Unable to load description URL", e); } JLabel lblMessage = getComponentFactory().createLabel(getFirstMessage()); lblMessage.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0)); JPanel panel = getComponentFactory().createPanel(new BorderLayout()); panel.add(spDescription); panel.add(lblMessage, BorderLayout.SOUTH); return panel; } }

20

CREATING A VIEW DESCRIPTOR FOR A VIEW


To show a view on screen, you need to create a view descriptor. For the initial view in the example, the following view descriptor was used:
<bean id="initialView" class="org.springframework.richclient.application.support.DefaultViewDescriptor"> <property name="viewClass" value="org.springframework.richclient.samples.simple.ui.InitialView" /> <property name="viewProperties"> <map> <entry key="firstMessage" value="firstMessage.text" /> <entry key="descriptionTextPath" value="org/springframework/richclient/samples/simple/ui/initialViewText.html" /> </map> </property> </bean>

This is the standard view descriptor definition. You need to give the view class to be used to show the component. Additionally, you can set the properties on an instance of that view by populating the viewProperties map. These properties have to correspond to standard JavaBeans property setters. In this case, the view class has a setFirstMessage and a setDescriptionTextPath method.

SHOWING THE VIEW IN THE APPLICATION


Setting the current view is done by using a ShowViewCommand. This command sets the view of the application window in which the command is located. To create such a command, use the following bean definition:
<bean id="initialCommand" class="org.springframework.richclient.command.support.ShowViewCommand"> <property name="viewDescriptor" ref=initialView /> </bean>

You can now use this command in your menu, or create a button in another view to switch to the defined view.

21

BINDING AND FORMS FORMMODELS AND VALUEMODELS WHAT IS A VALUEMODEL


A valuemodel provides a way for listening for changes on a property. In its simplest form, it is a property listener wrapper around a particular property. Its purpose is to track change, so that secondary functionality such as undo functionality and validation can be provided.

WHAT IS A FORMMODEL
A formmodel is a wrapper around a particular instance of an object. In essence, it is a consisting of valuemodels for the various properties of an object. It handles the overall state of the object.

THE DEFAULT FORMMODEL


The default formmodel in Spring Rich Client handles more than just the overall state of the object by managing its valueobject. It also provides : Buffering of values, effectively providing undo functionality Dirty tracking Validation possibilities through validators Set certain properties of an object to read-only, even if they have setters Creating a formmodel of any given object can be done through this:
MyObject object = new MyObject(); ValidatingFormModel model = new DefaultFormModel(object);

From then on, you can set the values of the object through the valueobject.
model.getValueModel(someProperty).setValue(xyz)

Valuemodels in formmodels are created on-demand. After creating a formmodel on an object, no valuemodels are present until you start calling for them. Spring Rich Client will then make these on-demand.

BUFFERING
Buffering provides the necessary plumbing needed for undo functionality. When changing values of property, a buffered valuemodel will still hold the old values and can revert to these if necessary.
MyObject object = new MyObject(); object.setXyz(xyz); ValidatingFormModel model = new DefaultFormModel(object); model.getValueModel(xyz).setValue(abc); // object hasnt changed, object.getXyz() will return xyz model.commit(); // object has changed, object.getXyz() will return abc

Calling revert() before a commit on a formmodel will return all properties to their original values. Individual valuemodels can be reverted too by calling revert() on them.

22

READ-ONLY MANIPULATION
An entire formmodel can be set to be read-only by using the setReadOnly() method. Setting individual properties read-only is a little bit more complicated. Out of the box, Spring Rich Client will inspect the object and determine whether a property is read-only, based on the existence of a setter method for that property. However, there might be cases where youd want to deliberately change the read-only behavior of a property, even if it has a setter. The fact whether a property is set as read-only is held by field metadata. For any given property you can ask the formmodel for the field metadata by calling
FieldMetaData meta = model.getFieldMetaData(xyz);

Through this field metadata, you can set the read-only property of a property
meta.setReadOnly(true);

Obviously, trying to set a property that has no setter to writable will cause an exception when the valuemodels are committed (and the respective setters are called).

VALIDATION
The default form model also contains functionality for validating the enclosed values. The validation is done through Spring Rich Clients own validation subsystem by utilizing validators. Well discuss the details of these validators in detail later. When a property is changed, the validator will be called to check whether the object is still in a consistent state. If not, the validator will produce validation errors, which then can be showed to the user through various means. Setting a validator on a formmodel is done through
model.setValidator(someValidator)

After that, the validation is automatically turned on. If you needed to, you could turn it off by calling
model.setValidating(false)

A model can be validated at any time. A model is aware whether has validation errors, and if so, contains a collection of these. For more information on this, I refer to the JavaDocs on ValidatingFormModel and DefaultFormModel

CREATING FORMMODELS
To create a formmodel, Spring Rich Client has provided a factory class that can create various formmodels. This class is called FormModelHelper. For example, if you want to create a formmodel of an object, the simplest way would be:
FormModelHelper.createFormModel(new SomeObject(), formModelId);

With the FormModelHelper, you can create: Default formmodels (with validation and buffering) Unbuffered formmodels Child formmodels of existing formmodels Formmodels, at this time, are object based. To create a formmodel, you need to be able to create an object of the class to be utilized by the formmodel.

23

There are implementations on the way to make these class-based, but these are still in development and shaky at best at the moment.

BINDING WHAT IS BINDING


Binding in Spring Rich Client encompasses the connection between a visual component and the state of a certain property.

HOW DOES BINDING WORK AND WHAT DOES IT DO


Binding is done through a valuemodel. A binding covers only one property at a time, most of the time (a binding could be done by aggregating different valuemodels, but thats way out of scope for this introduction). A binding will transfer all property changes to the object behind it, and vice versa. Its bound to a particular formmodel and property (and therefore, a valuemodel), and is responsible for creating the visual component

BINDERS
Binders a factories for bindings. Generally, for each sort of binding youll use in your application, youll have one (or more, if there are specific variants of certain bindings that may be occurring).

BINDER EXAMPLES
In Spring Rich Client, a number of binders have been implemented out of the box. TextComponentBinder: can handle text-type variables like strings CheckBoxBinder: can handle Boolean-type variables ListBinder: can handle lists And many more For Java 5+ there is even a binder available for enums, which visually is represented by a combobox.

CREATING YOUR OWN BINDER


Say we want to create a binder for JodaTimes classes. Javas standard date classes are bad to work with, so this example may even be somewhat useful. We start of by creating a the binder. A binder is able to bind any JComponent to a value, so well use SwingXs JXDatePicker class to visually represent the date.
public class JodaTimeDateTimeBinding extends CustomBinding implements PropertyChangeListener { private final JXDatePicker datePicker; private final boolean readOnly; private boolean isSettingText = false; public JodaTimeDateTimeBinding(FormModel model, String path, JXDatePicker datePicker, boolean readOnly) { super(model, path, DateTime.class); this.datePicker = datePicker; this.readOnly = readOnly; } @Override protected void valueModelChanged(Object newValue) { isSettingText = true;

24

setDatePickerValue((DateTime) newValue); readOnlyChanged(); isSettingText = false; } private void setDatePickerValue(DateTime dateTime) { if (dateTime == null) { datePicker.setDate(null); } else { datePicker.setDate(dateTime.toDate()); } } @Override protected JComponent doBindControl() { setDatePickerValue((DateTime) getValue()); datePicker.getEditor().addPropertyChangeListener("value", this); return datePicker; } public void propertyChange(PropertyChangeEvent evt) { if (!isSettingText && !isReadOnly()) controlValueChanged(new DateTime(datePicker.getDate())); } @Override protected void readOnlyChanged() { datePicker.setEditable(isEnabled() && !this.readOnly && !isReadOnly()); } @Override protected void enabledChanged() { datePicker.setEnabled(isEnabled()); readOnlyChanged(); } }

As you can see the class does the 2 way binding. This part
@Override protected void valueModelChanged(Object newValue) { isSettingText = true; setDatePickerValue((DateTime) newValue); readOnlyChanged(); isSettingText = false; }

handles the propagation of changes in the formmodel to the actual component, whereas the property change listener (which in this case is the binder itself, handled by
public void propertyChange(PropertyChangeEvent evt) { if (!isSettingText && !isReadOnly()) controlValueChanged(new DateTime(datePicker.getDate())); }

The isSettingText flag is there to prevent cyclic calls (formmodel changes component, which change the formmodel, which changes, ).

25

Binding the control to the value is done through the doBindControl() method. This method is called to wire the component to the binding and prepares all the plumbing to make the binding work. Creating the binder is most of the time the easiest job of the two.
public class JodaTimeDateTimeBinder extends org.springframework.richclient.form.binding.support.AbstractBinder { private boolean defaultsSet = false; private boolean readOnly = false; public JodaTimeDateTimeBinder() { super(DateTime.class); } public void setReadOnly(boolean readOnly) { this.readOnly = readOnly; } @SuppressWarnings("unchecked") protected JComponent createControl(Map context) { JXDatePicker datePicker = new JXDatePicker(); datePicker.setEditor(new DateTextField()); return datePicker; }

@SuppressWarnings("unchecked") protected Binding doBind(JComponent control, FormModel formModel, String formPropertyPath, Map context) { if (!defaultsSet) { Map<Object, Object> defaults = UIManager.getDefaults(); defaults.put("JXDatePicker.longFormat", "EEE dd/MM/yyyy"); defaults.put("JXDatePicker.mediumFormat", "dd/MM/yyyy"); defaults.put("JXDatePicker.shortFormat", "dd/MM"); defaultsSet = true; } return new JodaTimeDateTimeBinding(formModel, formPropertyPath, ((JXDatePicker) control), this.readOnly); } }

The createControl() method creates the control that is to be used in bindings. Every time a binding is done, a new control will be created through this method. The actual binding is done through the doBind(). It will create a binding, do some specific behavior in some case (here were manipulating some UI properties to alter the JXDatePickers appearance.

FORMS
Now that we have covered the formmodels and the binding, we can now cover the combination of the both.

WHAT IS A FORM
Whereas a binding covers a single property, a form covers an entire object. It can contain many bindings, backed up by a formmodel that wrapped the forms object.

CREATING A FORM
Forms are created for a specific purpose and specific objects. Say we have the following object:
public class TestObject

26

{ private String field1; private String field2; public String getField1() { return field1; } public void setField1(String field1) { this.field1 = field1; } public String getField2() { return field2; } public void setField2(String field2) { this.field2 = field2; } }

If we want to make a form for this, well might be using something like this
public class TestForm extends AbstractForm { public TestForm() { super(FormModelHelper.createFormModel(new TestObject(), "testForm")); } protected JComponent createFormControl() { JPanel content = new JPanel(); content.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); content.setLayout(new FormLayout( new ColumnSpec[] { FormFactory.DEFAULT_COLSPEC, FormFactory.LABEL_COMPONENT_GAP_COLSPEC, FormFactory.DEFAULT_COLSPEC }, new RowSpec[] { FormFactory.DEFAULT_ROWSPEC, FormFactory.LINE_GAP_ROWSPEC, FormFactory.DEFAULT_ROWSPEC } )); TextComponentBinder binder = new TextComponentBinder(); Map map = new HashMap(); content.add(new JLabel("Field 1"), new CellConstraints(1, 1)); content.add(binder.bind(getFormModel(), "field1", map).getControl(), new CellConstraints(3, 1)); content.add(new JLabel("Field 2"), new CellConstraints(1, 3)); content.add(binder.bind(getFormModel(), "field2", map).getControl(), new CellConstraints(3, 3)); return content; } }

This will result in a panel with 2 text fields next to each other, that represent the 2 fields of the object. This form can then be used to show in a view or a dialog. Currently, there is no default view descriptor for forms, since these are mostly contained in views in which they only make up a part of the screen (for example, in combination with a table). 27

FORM BUILDERS
As shown in the example above, forms can be created by using binders and bindings directly. However, for more elaborate form, this method is not really usable (or readable for that matter). To tackle this problem, Spring Rich Client has created form builders. Form builders make form creation a lot easier by providing simple addition of properties, labels and other component to forms. Form builders use the binding factory facilities built into Spring Rich Client. The binding factory system can set default binders for certain types, so that you dont need to worry how something should look. It can also provide aliases for binders defined in the context, so that you can use these swiftly. Building the same form with a form builder would result in
public class TestForm extends AbstractForm { public TestForm() { super(FormModelHelper.createFormModel(new TestObject(), "testForm")); } protected JComponent createFormControl() { TableFormBuilder builder = new TableFormBuilder(getBindingFactory()); builder.add("field1"); builder.row(); builder.add("field2"); JPanel panel = (JPanel) builder.getForm(); panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); return panel; } }

Much simpler and easy to read, isnt it? An additional advantage in using a form builder is that internationalized labels are supported out of the box. In the form builder example no labels are coded, but the form builder will add them automagically. Currently there is a form builder that works with JGoodies FormLayout named TableFormBuilder, and there is also one that supports Javas GridbagLayout. You can always create your own form builder by extending AbstractFormBuilder.

BINDER SELECTION
Spring Rich Client has a mechanism to automatically choose binders based on property names, types or even the used Swing components. This is done through BinderSelectionStrategy implementations. The standard implementation is the SwingBinderSelectionStrategy, which already has support for String and Boolean type fields. If you want to expand this automatical binder selection, you can configure your own BinderSelectionStrategy in your Spring context (which will be picked up by the service locator), and set for example the bindersForPropertyTypes map property. This map matches property types to specific binders that you also have configured in your Spring context. For example, if you want to change support String, Boolean and Date fields, you can define your selection strategy like this
<bean id="binderSelectionStrategy" class="org.springframework.richclient.form.binding.swing.SwingBinderSelectionStrategy"> <property name="bindersForPropertyTypes" ref="propertyTypeBinders"/> </bean>

28

<util:map id="propertyTypeBinders" key-type="java.lang.Class"> <entry key="java.lang.String" value-ref="stringBinder"/> <entry key="java.util.Date" value-ref="dateBinder"/> <entry key="java.lang.Boolean" value-ref="booleanBinder"/> </util:map>

INTERNATIONALIZATION
Remember the id you can give to your formmodel? This is the part where its needed. Spring Rich Client will use the formmodels is to create the key itll use to look up the labels text. Say your formmodel is named personForm and you have a field called firstName. Then in your message bundle youll have to provide something like this:
personForm.firstName.label = First name

If no value is found for a key, Spring Rich Client will show the key instead. This way you can easily spot missing keys (and dont need to guess how they are named).

ADDING FORMS TO FORMS, AKA CHILD FORMS


Forms are just plain components. They can be added to forms as any other component. However, when creating a child form, you need to make sure the formmodel of the child form is also a child of the formmodel of that forms parent. That way, events are carried over correctly. Child forms can also be added by using the addChildForm() method on a form. This way, the formmodels between the two are automatically linked. Bear in mind though, a setFormObject on a parent form does not cause a setFormObject on its children. This is something youll have to handle yourself.

FORM VALIDATION
Form validation is done through the validation subsystem by validating the formmodel (and underlying valuemodels). Form component interceptors such as the OverlayValidationInterceptorFactory can then show the validation errors to the user. For more information on interceptor, read the next part.

29

FORM COMPONENT INTERCEPTORS INTRODUCTION


Form component interceptors provide a way to intercept and add extra functionality to input components on a form. The application context specifies the list of interceptors to attach to controls constructed by the platform. This allows for a declarative model for specifying "additional" functionality to be added to various components. Examples are interceptors to overlay validation error images and background color changes, provide popup menus in text fields, and autocompletion (as you type) for comboboxes.

CREATING YOUR OWN INTERCEPTOR


To create your own FormComponentInterceptor, you have to provide both a FormComponentInterceptor and a FormComponentInterceptorFactory implementation.

public interface FormComponentInterceptor { public void processLabel(String propertyName, JComponent label); public void processComponent(String propertyName, JComponent component); }

CONFIGURATION
The configuration of the interceptors FormComponentInterceptorFactory. Sample configuration:
<bean id="formComponentInterceptorFactory" class="org.springframework.richclient.form.builder.support.ChainedInterceptorFactory"> <property name="interceptorFactories"> <list> <bean class="org.springframework.richclient.form.builder.support.ColorValidationInterceptorFactory"> <property name="errorColor" value="255,200,200"/> </bean> <bean class="org.springframework.richclient.form.builder.support.OverlayValidationInterceptorFactory"/> <bean class="org.springframework.richclient.form.builder.support.DirtyIndicatorInterceptorFactory"/> <bean class="org.springframework.richclient.text.TextComponentPopupInterceptorFactory"/> <bean class="org.springframework.richclient.list.ComboBoxAutoCompletionInterceptorFactory"/> </list> </property> </bean>

in

the

application

context

is

done

by

defining

the

30

BUILT-IN INTERCEPTORS
There are a number of built-in interceptors provided with the framework. Well quickly explain them.

ERROR OVERLAY IMAGE


Shows an error image in the lower left corner of the component if the contents of the component is invalid. The image also has a tooltip showing the validation message.

This class has been specifically made to work with Spring Rich Clients validation framework and will show the errors coming from that framework To configure this interceptor, you need to use this interceptor factory:
<bean class="org.springframework.richclient.form.builder.support.OverlayValidationInterceptorFactory" />

ERROR BACKGROUND COLOR


Changes the background color of the form component when an invalid value is entered. Properties: errorColor: the background color To configure this interceptor, you need to use this interceptor factory:
<bean class="org.springframework.richclient.form.builder.support.ColorValidationInterceptorFactory"> <property name="errorColor" value="255,200,200"/> </bean>

TEXT EDITING POPUP (COPY, PASTE, UNDO,...)


Adds more advanced text editing functionality to text components. It adds a popup menu with "undo/redo/cut/copy/paste/select all" items. It also adds the standard keyboard accelerators for these commands to the component.

To configure this interceptor, you need to use this interceptor factory:


<bean class="org.springframework.richclient.form.builder.support.TextComponentPopupInterceptorFactory" />

31

COMBOBOX AUTOCOMPLETION
Adds auto completion to a combobox.

To configure this interceptor, you need to use this interceptor factory:


<bean class="org.springframework.richclient.form.builder.support.ComboBoxAutoCompletionInterceptorFactory" />

OVERLAY IMAGE INDICATING A CHANGED VALUE


Shows an image in the top left corner of the component if the contents of the component has been changed by the user. The image also has a tooltip showing the original value. To the right of the image is a small revert button. Pushing this button restores the original value in the component.

Properties: includedFormModelIds: list of form models that should display the Dirty Indicator. Only one of includedFormModelIds or excludedFormModelIds can be specified. excludedFormModelIds: list of form models that should not display the Dirty Indicator Only one of includedFormModelIds or excludedFormModelIds can be specified. To configure this interceptor, you need to use this interceptor factory:
<!-- The login form will not show the Dirty Indicator --> <bean class="org.springframework.richclient.form.builder.support.DirtyIndicatorInterceptorFactory"> <property name="excludedFormModelIds"> <list> <value>loginForm</value> </list> </property> </bean>

SELECT ALL TEXT


Selects all the text in text fields and spinners when they receive focus.

To configure this interceptor, you need to use this interceptor factory:


<bean class="org.springframework.richclient.form.builder.support.SelectAllInterceptorFactory" />

SETTING THE CARET TO THE BEGINNING OF THE FIELD


If the text is set in a text component, the caret position is set to the end of the text. This means the beginning of the text will not be visible if the text is too long to fit in the text component. This FormComponentInterceptor "fixes" this behavior, and sets the caret to position 0.

32

To configure this interceptor, you need to use this interceptor factory:


<bean class="org.springframework.richclient.form.builder.support.TextCaretFormComponentInterceptorFactory" />

SHOWING A TOOLTIP
If a form property has a caption defined in the messages.properties file it will be used as the tooltip for the form component. Properties: processComponent: default is true, determines whether the tooltip of the component is set. processLabel: default is true, determines whether the tooltip of the label is set. To configure this interceptor, you need to use this interceptor factory:
<bean class="org.springframework.richclient.form.builder.support.ToolTipInterceptorFactory" />

CHANGING THE RENDERING OF A CHECKBOX


Allows customization on how a CheckBox form property is rendered. Properties: showLabel: default is true, determines whether the label will be shown. showText: default is false, determines whether the label text will be used as text for the checkbox itself. textKey: default is "text", the key used to fetch the text to show. To configure this interceptor, you need to use this interceptor factory:
<bean class="org.springframework.richclient.form.builder.support.CheckBoxFormComponentInterceptorFactory" />

SHOW THE CAPTION OF THE CURRENTLY FOCUSED COMPONENT IN THE STATUSBAR


Shows the caption of the form component in the statusbar when the component is focused. To configure this interceptor, you need to use this interceptor factory:
<bean class="org.springframework.richclient.form.builder.support.ShowCaptionInStatusBarInterceptorFactory" />

SHOW THE DESCRIPTION OF THE CURRENTLY FOCUSED COMPONENT IN THE STATUSBAR


Shows the description of the form component in the statusbar when the component is focused. To configure this interceptor, you need to use this interceptor factory:
<bean class="org.springframework.richclient.form.builder.support.ShowDescriptionInStatusBarInterceptorFactory" />

33

VALIDATION WHY VALIDATION


Users make mistakes. They wont admit it, but they do. As GUI developers, our task is to make sure that their mistakes dont crash or cripple our system. Input validation can help a great deal with this. Spring Rich Client supports validators for various input. These are all subclasses of RichValidator.

VALIDATION CHOICES
Spring Rich Client has multiple validation strategies: The built-in rules system Using Hibernate Validator Using Spring Modules Valang validation framework A combination of any of the above

RULE VALIDATION USING THE RULE FRAMEWORK


Spring Rich Client has created their own validation framework, which is rule based. A change to a value it put through a number of checks, which are contained within a rule, and results in a success or a validation error. The FormModelHelper can create formmodel with rule validation by providing a RulesSource in one of its factory methods. A rule source contains different rules for a certain object type. If, for example, we want to validate that the field1 property of the TestObject class is never empty, we can create a rulesource like this:
DefaultRulesSource source = new DefaultRulesSource(); Rules rules = new Rules(TestObject.class); Constraints c = new Constraints(); rules.add(c.required("field1")); source.addRules(rules);

CONSTRAINTS
Constraints contain the actual logic that checks the values. The Constraints class contains a lot of predefined constraints. Among these you can find Maximum length of string Maximum value Not null Minimum value Creating your own constraint is done by implementing the Constraint interface, which consists of one method
public interface Constraint { boolean test(Object argument); }

This constraint will test any object. Most of the time, well want to split up functionality to check individual properties, so that we can reuse this logic elsewhere. Remember, the constraints you add to a rules source always need to be coupled to a property. 34

For this, youll need to subclass AbstractPropertyConstraint, which needs a property name. Also, in its test method, it provides an easy way to get values of individual properties. Say we want to create a constraint that checks whether a String propertys value equals RCP (silly, but a good example). Well end up with something like this:
public class RcpConstraint extends AbstractPropertyConstraint { protected boolean test(final PropertyAccessStrategy domainObjectAccessStrategy) { Object prop = domainObjectAccessStrategy.getPropertyValue(getPropertyName()); return !(prop instanceof String) || ((String) prop).equals("RCP"); } }

This constraint you can then add to your rulessource for a specific property.

VALIDATION TRIGGERS
Validation on a property is triggered when that property is changed in its valuemodel. Spring Rich Client will search for rules for that property and execute them.

DEPENDENT PROPERTIES
Some rules that are registered for a certain property need to be triggered when another property is changed (for example two dates, for which the first needs to be before the last). Spring Rich Client supports this by overriding the isDependentOn() method. Out-of-the-box, this method returns true if the parameters equals the property name for which the rule is defined. However, you can add additional properties to this method. Every change in a property that returns true on this method will cause this rule to be checked.

HIBERNATE VALIDATOR INTEGRATION


Hibernate Validator is a well-known framework for validating JavaBeans. Its annotation-based, so you wont find it in the standard Spring Rich Client, but support is provided in the jdk5 module. Simply put, if the object behind a form has Hibernate Validator annotations, by setting the validator to a HibernateRulesValidator. This validator will check all property-based validations (no @AssertTrue support at the moment yet). You can even turn off validation for certain properties (for example, a code is @NotNull, but in the GUI youre allowed to leave it empty because youll generate one on the fly when thats the case).

VALANG VALIDATION FRAMEWORK INTEGRATION


For more information on the Valang validation framework, see the Valang documentation. Converting the rules source example to Valang would result in something like this (Ill show the bean definition):
<bean name="testObjectValidator" class="org.springmodules.validation.valang.ValangValidator"> <property name="valang"> <value><![CDATA[ { field1 : (? IS NOT NULL) : 'Field 1 is required' : 'errors.required' : 'Field 1' } ]]>

You can then make a ValangRichValidator for a certain formmodel with that validator.

INTEGRATING YOUR OWN VALIDATION FRAMEWORK


If you want to integrate your own validation framework, youll need to subclass the RichValidator class and wire your validator to produce validation results.

35

EXCEPTION HANDLING AVOIDING TRY-CATCH CONSTRUCTS


Most runtime exceptions that are thrown are unexpected: we don't expect them to happen (especially during production) such as: NullPointerException: Didn't I double checked all my source code to avoid NPE's? CvsParserException: Why did the user pick a html file when I asked him for a CVS file? IDidNotKnowThisExistedRuntimeException: What the ...? And if you do expect some of them, you usually can't really fix the problem, just deal with it: Log the exception through a logging framework such as SLF or Log4J. Notify the user that whatever he tried didn't work, preferably with an not-technical, exception-specific explanation. Either shutdown the application or allow the user to continue (and try again). You could use try-catch during every user action:
protected boolean onFinish() { try { form.getFormModel().commit(); // ... getApplicationContext().publishEvent(new LifecycleApplicationEvent(eventType, getEditingContact())); return true; } catch (Throwable throwable) { handleException(throwable); } }

But this is tedious and error prone: It's easy to forget to try catch some code, which makes the exception escape to the top layer exception handler. You could unwillingly eat the exception or not log it: o If you handle an exception, but forget to log it and/or show it to the user. o If you throw an exception in the catch or finally part, only the last exception bubbles up, effectively hiding the real exception. In production, this leads to discussions where the user is sure he did perform an action (which he did in this case) and the programmer is sure the user didn't because the system didn't report anything and nothing has changed. If you notice that while you are fixing a issue and an exception is being eaten (making it hard to identify the original issue), create a new issue because exceptions are eaten and fix that first. You are in danger to handle the same exception on 2 different layers, effectively logging it or notifying the user twice. In some layers or parts of the application, it might not be clear if you need to notify the user (and which user) through a swing dialog or JSP or web service response. Spring Rich Clients exception handling system uses the top layer exception handling. It expects that all other layers let the exception bubble up.

REGISTERING AN EXCEP TION HANDLER


In the LifecycleAdvisor you can register a RegisterableExceptionHandler:
<bean id="lifecycleAdvisor" class="...LifecycleAdvisor"> <!-- ... --> <property name="registerableExceptionHandler" ref="exceptionHandler" /> </bean> <bean id="exceptionHandler" class="org.springframework.richclient.exceptionhandling.SilentExceptionHandler"/>

36

When an exception handler is registered, it will usually register itself as the UncaughtExceptionHandler on the threads. However, the event thread catches a throwable thrown in any event, to prevent getting killed, so it also registers itself to the event thread specifically (regrettably this is currently Sun JRE specific behavior).

BUILD-IN EXCEPTION HANDLERS


Most of these exception handlers are part of the spring-richclient-jdk5 module.

LOG SILENTLY
Logs a throwable but does not notify the user in any way. Normally it is a bad practice not to notify the user if something goes wrong. You can set a log level on it:
<bean class="org.springframework.richclient.exceptionhandling.SilentExceptionHandler"> <property name="logLevel" value="WARN"/> <!-- ... --> </bean>

This means that any exception handled by this exception handler will be logged at the warn level.

SHOW A MESSAGE DIALOG


Shows the exception in a dialog to the user (as well as logging it). You can set a log level and the icon of the dialog depends on that log level. The shown dialog has a caption (= dialog title) and description (= dialog content), which are fetched from the i18n messages files. There are 2 ways to resolve those messages: static or dynamic (default). You can statically set the title and description by setting the messagesKey property. However, it's a lot more powerful to use the default dynamic behavior based on the class of the exception. For example if a NumberFormatException is thrown, it will first look for these i18n keys:
java.lang.NumberFormatException.caption=Not a number java.lang.NumberFormatException.description=\ You did not enter a a valid number.\n\ \n\ Please enter a valid number.

If these messages keys don't exist, it will fall back to the parent class of NumberFormatException, which is IllegalArgumentException:
java.lang.IllegalArgumentException.caption=... java.lang.IllegalArgumentException.description=...

It will continue to fall back up the chain, untill it reaches Throwable. This allows you to direct all unexpected exceptions (for example IDidNotKnowThisExistedRuntimeException) to a MessagesDialogExceptionHandler that logs them as an error and shows a generic message. You can even use {0} in your i18n message to show the exception.getMessage() in the description:
# Double quotes(") need to be escaped (\"), single quotes (') always seem to break the replacing of {0}. java.lang.RuntimeException.caption = Unexpected general bug java.lang.RuntimeException.description = \ The application experienced an unexpected bug,\n\ due to a programming error.\n\ \n\ The application is possibly in an inconsistent state.\n\ It is recommended to reboot the application.\n\ \n\ The exact bug is:\n\ {0}\n\ \n\

37

Please report this bug. java.lang.Error.caption = Unexpected serious system failure java.lang.Error.description = \ A serious system failure occured.\n\ \n\ The application is possibly in an inconsistent state.\n\ Reboot the application.\n\ \n\ The exact bug is:\n\ {0}\n\ \n\ Please report this bug.

Note that, although this dynamic system is pretty powerful and avoids a lot of boilerplate, it's usually not a replacement for DelegatingExceptionHandler, because it doesn't allow you to assign different log levels, etc. You can set a shutdown policy on a dialog exception handler:
<bean class="org.springframework.richclient.exceptionhandling.MessagesDialogExceptionHandler"> <property name="shutdownPolicy" value="ASK" /> <!-- ... --> </bean>

This allows you to optionally enforce or propose a System.exit(1).

HIBERNATE VALIDATION EXCEPTION HANDLING


A special exception handler which can only handle an InvalidStateException thrown by Hibernate validator. It shows the failed validations to a user in a list in a dialog. In most cases it's inferior to the HibernateRulesValidator which validates before the user presses the commit button. But because the latter forces you to hand code @AssertTrue's and it could be working on stale client-side data, it's actually a very nice backup to also configure this exception handler:
<!-- Inside a delage list of DelegatingExceptionHandler --> <bean class="org.springframework.richclient.exceptionhandling.delegation.SimpleExceptionHandlerDelegate"> <property name="throwableClass"> <value type="java.lang.Class">org.hibernate.validator.InvalidStateException</value> </property> <property name="exceptionHandler"> <bean class="org.springframework.richclient.exceptionhandling.HibernateValidatorDialogExceptionHandler"> <property name="logLevel" value="INFO" /> <property name="shutdownPolicy" value="NONE" /> </bean> </property> </bean>

CUSTOM EXCEPTION HANDLERS


You can also extend AbstractLoggingExceptionHandler and implement this method:
public void notifyUserAboutException(Thread thread, Throwable throwable) { // ... }

This way, you could for example integrate e-mail functionality, heck, even IM functionality to the error notification towards the user.

USING THE RIGHT EXCEPTION HANDLER FOR THE RIGHT EXCEPTION


There are bunch of build-in exception handlers, but usually there isn't one exception handler that fits alls exceptions. A DelegatingExceptionHandler allows you to delegate an exception to the right exception handler. It accepts a delegate list and traverses that list in order. The first delegate that can handle the exception, has to handle the exception and the rest of the delegate list is ignored. 38

For example, here we configure authentication and authorization exceptions to a MessagesDialogExceptionHandler with WARN messages, Hibernate exceptions to an INFO HibernateValidatorDialogExceptionHandler and the rest to an ERROR MessagesDialogExceptionHandler.
<bean id="exceptionHandler" class="org.springframework.richclient.exceptionhandling.delegation.DelegatingExceptionHandler"> <property name="delegateList"> <list> <bean class="org.springframework.richclient.exceptionhandling.delegation.SimpleExceptionHandlerDelegate"> <property name="throwableClassList"> <list> <value type="java.lang.Class">org.acegisecurity.AuthenticationException</value> <value type="java.lang.Class">org.acegisecurity.AccessDeniedException</value> </list> </property> <property name="exceptionHandler"> <bean class="org.springframework.richclient.exceptionhandling.MessagesDialogExceptionHandler"> <property name="logLevel" value="WARN" /> <property name="shutdownPolicy" value="NONE" /> </bean> </property> </bean> <bean class="org.springframework.richclient.exceptionhandling.delegation.SimpleExceptionHandlerDelegate"> <property name="throwableClass"> <value type="java.lang.Class">org.hibernate.validator.InvalidStateException</value> </property> <property name="exceptionHandler"> <bean class="org.springframework.richclient.exceptionhandling.HibernateValidatorDialogExceptionHandler"> <property name="logLevel" value="INFO" /> <property name="shutdownPolicy" value="NONE" /> </bean> </property> </bean> <!-- The order is important: if Throwable would be first then the others would be ignored --> <bean class="org.springframework.richclient.exceptionhandling.delegation.SimpleExceptionHandlerDelegate"> <property name="throwableClass" value="java.lang.Throwable" /> <property name="exceptionHandler"> <bean class="org.springframework.richclient.exceptionhandling.MessagesDialogExceptionHandler"> <property name="logLevel" value="ERROR" /> <property name="shutdownPolicy" value="ASK" /> </bean> </property> </bean> </list> </property> </bean>

SIMPLE DELEGATION
Processes the exception if it is an instance of throwableClass or the throwableClassList.

UNWRAPPING EXCEPTIONS
An exception purger allows you to cream off wrapper exceptions. This allows you to handle a chained exception in the chain of the uncaught exception, instead of the uncaught exception itself. Almost all exception handlers and delegate's support the use of a purger. DefaultExceptionPurger supports 2 ways to identify the depth to cream off: include or exclude based. A chained exception of the type in the includeThrowableClassList is stripped from all it's wrapper exceptions and handled by the exception handler or evaluated by the delegate. For example, we want to handle every MySQLIntegrityConstraintViolationException even if it's wrapped:

39

<bean class="org.springframework.richclient.exceptionhandling.delegation.SimpleExceptionHandlerDelegate"> <property name="exceptionPurger"> <bean class="org.springframework.richclient.exceptionhandling.delegation.DefaultExceptionPurger"> <property name="includeThrowableClassList"> <list> <value type="java.lang.Class">com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException</value> </list> </property> </bean> </property> <property name="throwableClassList"> <list> <value type="java.lang.Class">com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException</value> </list> </property> <property name="exceptionHandler"> <!-- ... --> </property> </bean>

A chained exception of the type in the excludeThrowableClassList is stripped together with all it's wrapper exceptions and it's cause is handled by the exception handler or evaluated by the delegate. For example the server wraps all exceptions in an annoying, useless WrappingServiceCallException and we want to get rid of it:
<bean id="exceptionHandler" class="org.springframework.richclient.exceptionhandling.delegation.DelegatingExceptionHandler"> <property name="exceptionPurger"> <bean class="org.springframework.richclient.exceptionhandling.delegation.DefaultExceptionPurger"> <property name="excludeThrowableClassList"> <list> <value type="java.lang.Class">foo.bar.WrappingServiceCallException</value> </list> </property> </bean> </property> <property name="delegateList"> <!-- ... --> </property> </bean>

40

SECURITY INTEGRATING SECURITY IN A GUI APPLICATION


Integrating security into a GUI application can be cumbersome. The reason for this is: Adding login and logout functionality can take a lot of work Adjusting screens to handle with security limitations Spring Rich Client integrates with Spring Security to provide a framework to handle security-based problems and offers a solution for these.

SPRING SECURITY INTEGRATION


Simply integrating login functionality is easy: provide a login command in your commands context and make your lifecycle advisor runs this command before showing the screen.
<bean id="loginCommand" class="org.springframework.richclient.security.LoginCommand"/>

public class PetClinicLifecycleAdvisor extends DefaultApplicationLifecycleAdvisor { // omitted for brevity public void onCommandsCreated(ApplicationWindow window) { ActionCommand command = (ActionCommand) window.getCommandManager().getCommand("loginCommand", ActionCommand.class); command.execute(); } }

The login command uses service location to find the application security manager. If no security manager is found in the context, the default implementation will be used.

TURNING ON SECURITY AWARENESS OF THE GUI


Spring Rich Client has a bean post processor that can enable any class to be notified of security changes. You can define this post processor as
<bean class=org.springframework.richclient.security.SecurityAwareConfigurer/>

41

If your classes implement either the AuthenticationAware or LoginAware interface, this bean postprocessor will make sure these get notified of login changes.

SECURING COMMANDS
Securing commands is easy. AbstractCommand enables you to define a security controller (by its id), which then uses a access decision manager to decide whether a command is allowed to be executed (by enabling/disabling it). For example, a secured command bean in the commands context may look like this:
<bean id="newOwnerCommand" class="org.springframework.richclient.command.TargetableActionCommand"> <property name="commandExecutor" ref="newOwnerWizard" /> <property name=securityControllerId value=mySecurityController/> </bean>

This assumes you have a security controller named mySecurityController defined in your application context, for example TODO: rest of the text

42

WIZARDS WHY USE WIZARDS


Wizards make flow-like form entry easy. If you have a flow of screens, with the input of the first screen affecting the behavior and possibly layout of one or more the next screens, wizards are an ideal way to accomplish this. Spring Rich Client has built-in wizard functionality.

CREATING A WIZARD
In Spring Rich Client, a wizard consists of wizard pages. Every wizard page can determine which page is next or previous and whether the wizard can stop at this page. Wizards are mostly form-based, which means validation is included as well. The wizard framework will not allow a user to change to another screen as long as the current is invalid. Creating a wizard is quite straightforward. You create pages (or even just forms) and add them to a wizard. Creating wizard will most of the time mean subclassing the AbstractWizard class and building a wizard as a separate component.
public class MyWizard extends AbstractWizard { private MyForm form1; private MyOtherForm form2; public MyWizard() { initializeForms(); addForm(form1); addForm(form2); } protected boolean onFinish() { form1.commit(); form2.commit(); doSomeLogic(); } }

This wizard will make 2 forms and when the wizard completes (the user presses finish), itll do some logic.

SHOWING A WIZARD
There are various ways for showing a wizard. Standard, Spring Rich Client provides you with a dialog class that can show a wizard (which most of the time is the way youll show a wizard). You can off course create a view class for your wizard, so you can display your wizard as a view too. To implement this, take a look at WizardDialog.

43

Vous aimerez peut-être aussi