Spring Rich Client
Spring Rich Client
Spring Rich Client
A practical introduction
Lieven DOCLO
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.
<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
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
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.
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.
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() { ... } } }
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
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; }
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"/>
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
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.
After the command has been configured, it can be used to create components.
Both methods have various parameter configurations, for more information on which to use in your scenario I refer to the JavaDoc documentation.
17
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.
18
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.
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
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
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.
You can now use this command in your menu, or create a button in another view to switch to the defined view.
21
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.
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.
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.
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).
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
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.
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" />
31
COMBOBOX AUTOCOMPLETION
Adds auto completion to a combobox.
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>
32
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" />
33
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
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.
You can then make a ValangRichValidator for a certain formmodel with that validator.
35
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.
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).
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.
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 way, you could for example integrate e-mail functionality, heck, even IM functionality to the error notification towards the user.
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
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.
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
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