Introduction
This article provides an introductory knowledge on Spring Rich client framework for building rich Desktop applications. This article heavily uses core Spring’s configuration based approach for defining Spring beans and Dependency injection. We will develop a simple User Management Application which will provide the functions for adding, creating, updating, deleting and listing user details in this article.
It will also provide the facility for doing CRUD operation on User Group objects. Associating User with User Group object with the help of Spring binding framework is also discussed. If the reader is not familiar with core Spring concepts, reference for the same is present here. We will parallely discuss the various concepts and components of Spring when developing the application.
also read:
Components
As part of this project, we will develop the following components and later part of the article shows how to make association between these components
- Model components
- Filter components
- Service components
- Form components
- Filter Form components
- Data Provider components
- Data Editor components
- Command components
- Application component
The component models, filters and service components are non Spring-RCP components which means that when you develop those components you have to depend on (Or) have to the include the Spring RCP library in the application’s class path, whereas to develop the rest of the components, Spring RCP libraries have to be included in the class path.
Downloading Spring RCP
The example shown in the article is developed using Spring RCP 1.10 and this can be downloaded from here. Include all the jars files along with the Spring RCP framework for developing the project and the following jar files:
- spring-beans-2.5.6.jar
- spring-context-2.5.6.jar
- spring-core-2.5.6.jar
- commons-dbcp-1.2.2.jar
- slf4j-simple-1.5.2.jar
- slf4j-api-1.5.2.jar
- commons-pool-1.2.jar
Model components
In this section, we will see how to develop the model components User and User Group. We assume that the properties id, name and date of birth are applicable for the User object. Also User object will include a reference to the User Group object indicating the group that a user belongs to. For the User Group object – id, name, description
are the properties. The User Group object will also include references pointing to the list of user objects belonging to the group. Since the properties id
and name
are common across User and User Group objects, we will define a base class containing these properties.
BaseModel.java
package net.javabeat.article.spring.rcp.introduction.usermgm.model; public abstract class BaseModel { private Long id; private String name; public BaseModel(){ } public BaseModel(Long id, String name){ this.id = id; this.name = name; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
Now we will see the code listing for the User class. Note that the following User
class extends BaseModel
class thereby inherting the id
and the name
properties.
User.java
package net.javabeat.article.spring.rcp.introduction.usermgm.model; import java.util.Date; public class User extends BaseModel implements Comparable{ private Date dateOfBirth; private UserGroup userGroup; public User(){ super(); } public User(Long id, String name, Date dateOfBirth){ super(id, name); this.dateOfBirth = dateOfBirth; } public Date getDateOfBirth() { return dateOfBirth; } public void setDateOfBirth(Date dateOfBirth) { this.dateOfBirth = dateOfBirth; } public UserGroup getUserGroup() { return userGroup; } public void setUserGroup(UserGroup userGroup) { this.userGroup = userGroup; this.userGroup.getUsers().add(this); } @Override public int compareTo(User user){ return getName().compareTo(user.getName()); } }
Note that the above model class implements the Comparable
interface which compares user objects based on the name. Later on in this article, we will see why the model objects implement the Comparable interface. Have a look at the UserGroup class,
UserGroup.java
package net.javabeat.article.spring.rcp.introduction.usermgm.model; import java.util.HashSet; import java.util.Set; public class UserGroup extends BaseModel implements Comparable{ private String description; private Set users; public UserGroup(){ super(); users = new HashSet(); } public UserGroup(Long id, String name){ super(id, name); users = new HashSet(); } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public Set getUsers() { return users; } public void setUsers(Set users) { this.users = users; } @Override public int compareTo(UserGroup userGroup){ return getName().compareTo(userGroup.getName()); } public String toString(){ return getName(); } }
Now that we have written the model objects, we will use Spring’s configuration based approach for declaring the model objects in the file models.xml.
models.xml
<?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/spring-beans-2.5.xsd"> <bean name="baseModel" scope="prototype"></bean> <bean name="user" scope="prototype"></bean> <bean name="userGroup" scope="prototype"></bean> </beans>
Filter components
Before writing filter components, we need to understand the purpose of filter components. In our User Management application, imagine that we have 100 user objects displayed in the UI – for example in a Table. We are also going to give the feature of filtering the user objects based on the search expression given by the user of the application. This feature will be done in so-called Filter forms (we will discuss about Filter forms more in the later section of the article). So, through the filter form if the user types ‘J’, then all the users whose name contain the text ‘J’ will be displayed. Have a look at the below listing.
AbstractFilter.java
package net.javabeat.article.spring.rcp.introduction.usermgm.filter; import net.javabeat.article.spring.rcp.introduction.usermgm.model.BaseModel; public class AbstractFilter{ private String nameContains; public String getNameContains(){ return nameContains; } public void setNameContains(String nameContains){ this.nameContains = nameContains; } public static void updateFilter(AbstractFilter filter, M model){ filter.setNameContains(model.getName()); } @SuppressWarnings("unchecked") public AbstractFilter fromModel(M model){ AbstractFilter filter = new AbstractFilter(); filter.setNameContains(model.getName()); return (AbstractFilter)filter; } }
Because we are going to provide the feature of filtering User and User Group objects through user name and user group name, we have written the above class which defines the property nameContains
indicating that we are going to have name based filtering. Have a look at the concrete implementation for the UserFilter
class.
UserFilter.java
package net.javabeat.article.spring.rcp.introduction.usermgm.filter; import net.javabeat.article.spring.rcp.introduction.usermgm.model.User; public class UserFilter extends AbstractFilter{ public UserFilter fromModel(User user){ UserFilter filter = new UserFilter(); updateFilter(filter, user); return filter; } }
The above filter class provides a method for creating the filter for the corresponding model objects. When the user types ‘J’ in the filter form and because the intention is to search the user name containing the text ‘J’ we will create a filter object containing this information. This filter object will be checked against all the user objects that will get fetched from some data source. The following listing provides details on UserGroup filter.
UserGroupFilter.java
package net.javabeat.article.spring.rcp.introduction.usermgm.filter; import net.javabeat.article.spring.rcp.introduction.usermgm.model.UserGroup; public class UserGroupFilter extends AbstractFilter{ public UserGroupFilter fromModel(UserGroup userGroup){ UserGroupFilter filter = new UserGroupFilter(); updateFilter(filter, userGroup); return filter; } }
We will configure the above components as Spring beans in the file filters.xml.
filters.xml
<?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/spring-beans-2.5.xsd"> <bean name="userFilter" scope="prototype"> </bean> <bean name="userGroupFilter" scope="prototype"> </bean> </beans>
Service components
Service components provide services for creating, updating, deleting and retrieving the model objects from a datastore. In this example application, we will be making use of Map collection for storing the model objects. The following class declares the Abstract Service class,
AbstractService.java
package net.javabeat.article.spring.rcp.introduction.usermgm.service; import net.javabeat.article.spring.rcp.introduction.usermgm.model.BaseModel; public abstract class AbstractService { public abstract M save(M baseModel); public abstract void delete(M baseModel); public abstract BaseModel getModel(Long id); }
One concrete implementation for the above service class will be the User Service class which is defined below.
UserService.java
package net.javabeat.article.spring.rcp.introduction.usermgm.service; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import net.javabeat.article.spring.rcp.introduction.usermgm.filter.UserFilter; import net.javabeat.article.spring.rcp.introduction.usermgm.model.User; public class UserService extends AbstractService{ private static Map<Long, User> DATABASE = new HashMap<Long, User>(); static{ populateUserMap(); } @SuppressWarnings("deprecation") private static void populateUserMap(){ User user = null; user = user(1L, "John", new Date(1990, 1, 1)); DATABASE.put(user.getId(), user); user = user(2L, "Ricky", new Date(1990, 2, 2)); DATABASE.put(user.getId(), user); user = user(3L, "David", new Date(1990, 3, 3)); DATABASE.put(user.getId(), user); user = user(4L, "Peter", new Date(1990, 1, 1)); DATABASE.put(user.getId(), user); } private static User user(Long id, String name, Date dateOfBirth){ User user = new User(); user.setId(id); user.setName(name); user.setDateOfBirth(dateOfBirth); return user; } public User save(User user){ DATABASE.put(user.getId(), user); return user; } public void delete(User user){ DATABASE.remove(user); } public List findModels(final UserFilter filter){ List filtered = new ArrayList(); Iterator<Map.Entry<Long, User>> iterator = DATABASE.entrySet().iterator(); while (iterator.hasNext()){ Map.Entry<Long, User> entry = iterator.next(); User model = entry.getValue(); if (checkFilter(model, filter)){ filtered.add(model); } } return filtered; } public User getModel(Long id){ return DATABASE.get(id); } public boolean checkFilter(User user, UserFilter filter){ boolean nameOk = true; if (filter.getNameContains() != null) nameOk = user.getName().contains(filter.getNameContains()); return nameOk; } }
The methods in the above methods are the common methods that do the job of inserting, updating, retrieving and deleting the model objects. The method to note is the findModels() which takes UserFilter object as an argument. Remember that this filter object will be populated with the user typed-in text from the Filter form UI and this will be passed to this method.
Of course, this method will be called by other components that we will write, it won’t be called by the framework. Within the implementation of the method we check whether the User object that we have is eligible for filtering by calling the checkFilter() method where a comparison is done against the name property of the User object. The following code listing shows the definition of UserGroup object whose functionality almost mimic the one that we have written above for the User object.
UserGroupService.java
package net.javabeat.article.spring.rcp.introduction.usermgm.service; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import net.javabeat.article.spring.rcp.introduction.usermgm.filter.UserGroupFilter; import net.javabeat.article.spring.rcp.introduction.usermgm.model.UserGroup; public class UserGroupService extends AbstractService{ private static Map<Long, UserGroup> DATABASE = new HashMap<Long, UserGroup>(); static{ populateUserGroupMap(); } private static void populateUserGroupMap(){ UserGroup userGroup = null; userGroup = userGroup(1L, "Friends", "Contacts of friends"); DATABASE.put(userGroup.getId(), userGroup); userGroup = userGroup(2L, "Family", "Contacts of families"); DATABASE.put(userGroup.getId(), userGroup); } private static UserGroup userGroup(Long id, String name, String description){ UserGroup userGroup = new UserGroup(); userGroup.setId(id); userGroup.setName(name); userGroup.setDescription(description); return userGroup; } public UserGroup save(UserGroup userGroup){ DATABASE.put(userGroup.getId(), userGroup); return userGroup; } public void delete(UserGroup userGroup){ DATABASE.remove(userGroup); } public List findModels(final UserGroupFilter filter){ List filtered = new ArrayList(); Iterator<Map.Entry<Long, UserGroup>> iterator = DATABASE.entrySet().iterator(); while (iterator.hasNext()){ Map.Entry<Long, UserGroup> entry = iterator.next(); UserGroup model = entry.getValue(); if (checkFilter(model, filter)){ filtered.add(model); } } return filtered; } public UserGroup getModel(Long id){ return DATABASE.get(id); } public boolean checkFilter(UserGroup user, UserGroupFilter filter){ boolean nameOk = true; if (filter.getNameContains() != null) nameOk = user.getName().contains(filter.getNameContains()); return nameOk; } }
Create a file called services.xml and declare the above components as Spring beans.
services.xml
<?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/spring-beans-2.5.xsd"> <bean name="userService"> </bean> <bean name="userGroupService"> </bean> </beans>
Form components
Form components are GUI components and they are used for getting inputs from the user. In this section, we will develop two Form components – one for the User and another for the UserGroup. Since the properties id and name are common across User and User Group objects, we will define a base form that will populate the components for displaying id and name properties. Have a look at the following code,
AbstractForm.java
package net.javabeat.article.spring.rcp.introduction.usermgm.form; import javax.swing.JComponent; import com.jgoodies.forms.layout.FormLayout; import net.javabeat.article.spring.rcp.introduction.usermgm.model.BaseModel; import org.springframework.binding.form.FieldMetadata; import org.springframework.richclient.form.FormModelHelper; import org.springframework.richclient.form.TabbedForm; import org.springframework.richclient.form.builder.FormLayoutFormBuilder; public class AbstractForm extends TabbedForm{ private String tabName; public AbstractForm(String formId, M baseModel) throws Exception{ super(FormModelHelper.createFormModel(baseModel, formId)); } protected Tab[] getTabs(){ FormLayout layout = new FormLayout("default, 3dlu, fill:pref:nogrow", "default"); FormLayoutFormBuilder builder = new FormLayoutFormBuilder(getBindingFactory(), layout); addComponents(builder); return new Tab[] {new Tab(getTabName(), builder.getPanel())}; } protected void addComponents(FormLayoutFormBuilder builder){ builder.addPropertyAndLabel("id"); builder.nextRow(); JComponent nameComponent = builder.addPropertyAndLabel("name")[1]; builder.nextRow(); FieldMetadata idMetaData = getFormModel().getFieldMetadata("id"); idMetaData.setReadOnly(true); setFocusControl(nameComponent); } protected String getTabName(){ return tabName; } public void setTabName(String tabName) { this.tabName = tabName; } }
Note that all the form components will directly or indirectly use Form interface. There are several concrete and ready-to-use form implementations and the one that we have used is TabbedForm which provides multi-tab support for entering the user inputs. Every form is associated with a FormModel object and in the above class we have associated the abstract BaseModel to the form class in the constructor. The method getTabName()
is used to set the name of the tab. In our case, we will only have a single tab for accepting the user inputs. The method getTabs()
should be overridden which will return a list of tabs to be displayed in the form.
Note that we have used FormLayout and FormLayoutFormBuilder classes for populating the form components. In this abstract class we have populated the components for displaying id
and the name
properties by calling the addComponents()
method. For adding components to the form, we use the method addPropertyAndLabel()
by passing in the property name. This property name should match the one present in the model object and the method addPropertyAndLabel()
adds a label component for displaying the property name and a text component for holding the property value.
Calling nextRow()
on the builder object will place the virtual pointer that add components in the form in the next row so that when the second component is added it will appear in the next row. Because id represents unique id for identifying the object, we shouldn’t allow the user to modify its value and hence it should be a read-only property. We make a property as read-only by calling by method setReadOnly()
on a property’s metadata object.
Now that we have defined the above base class, we will write the UserForm class for adding properties specific to the User which are date of birth and the user group as follow.
UserForm.java
package net.javabeat.article.spring.rcp.introduction.usermgm.form; import net.javabeat.article.spring.rcp.introduction.usermgm.model.User; import org.springframework.richclient.form.builder.FormLayoutFormBuilder; public class UserForm extends AbstractForm{ public UserForm(String formId, User user) throws Exception{ super(formId, user); } protected void addComponents(FormLayoutFormBuilder builder){ super.addComponents(builder); builder.addPropertyAndLabel("dateOfBirth"); builder.nextRow(); builder.addPropertyAndLabel("userGroup"); } }
Note that for the User object, the properties id, name and dateOfBirth are in-built properties and the framework knows how to display the value from the model and how to update the model with the new value typed by the user in the UI. However for any custom property like userGroup, we have to provide custom binders, We will see how to provide that in the later section. The following listing provides the definition of the UserGroup form which adds the property description.
UserGroupForm.java
package net.javabeat.article.spring.rcp.introduction.usermgm.form; import net.javabeat.article.spring.rcp.introduction.usermgm.model.UserGroup; import org.springframework.richclient.form.builder.FormLayoutFormBuilder; public class UserGroupForm extends AbstractForm{ public UserGroupForm(String formId, UserGroup userGroup) throws Exception{ super(formId, userGroup); } protected void addComponents(FormLayoutFormBuilder builder){ super.addComponents(builder); builder.addPropertyAndLabel("description"); builder.nextRow(); } }
Now that we have defined all the form components, we will see how to declare them in the configuration file forms.xml
forms.xml
<?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/spring-beans-2.5.xsd"> <bean name="abstractForm" scope="prototype" abstract = "true"> <property name= "tabName" value="Detail" /> </bean> <bean name="userForm" scope="prototype"> <constructor-arg index = "0" value="userForm" /> <constructor-arg index = "1" ref="user" /> <property name= "tabName" value="User Detail" /> </bean> <bean name="userGroupForm" scope="prototype"> <constructor-arg index = "0" value="userGroupForm" /> <constructor-arg index = "1" ref="userGroup" /> <property name= "tabName" value="User Group Detail" /> </bean> </beans>
Filter form components
As discussed already, Filter forms provide the benefit of filtering records in an UI containing some thousand records. Have a look at the following listing,
A.java
package net.javabeat.article.spring.rcp.introduction.usermgm.filterform; import com.jgoodies.forms.layout.FormLayout; import net.javabeat.article.spring.rcp.introduction.usermgm.filter.AbstractFilter; import net.javabeat.article.spring.rcp.introduction.usermgm.model.BaseModel; import org.springframework.richclient.form.FilterForm; import org.springframework.richclient.form.builder.FormLayoutFormBuilder; import javax.swing.*; public class AbstractFilterForm> extends FilterForm{ private F filter; public AbstractFilterForm(String filterFormId){ super(filterFormId); } @Override protected F newFormObject(){ return filter; } public F getFilter() { return filter; } public void setFilter(F filter) { this.filter = filter; } @SuppressWarnings("unchecked") @Override public void setFormObject(Object formObject){ if (getModel().isInstance(formObject)){ super.setFormObject(filter.fromModel((M)formObject)); }else{ super.setFormObject(formObject); } } protected JComponent createFormControl(){ FormLayout layout = new FormLayout("default, 3dlu, fill:pref:nogrow", "default"); FormLayoutFormBuilder builder = new FormLayoutFormBuilder(getBindingFactory(), layout); addComponents(builder); return builder.getPanel(); } protected void addComponents(FormLayoutFormBuilder builder){ builder.addPropertyAndLabel("nameContains"); builder.nextRow(); } protected Class<? extends BaseModel> getModel(){ return BaseModel.class; } }
Again, since User Filter and User Group filter share some common properties, we have created an AbstractFilter form class. This filter form is templated with the model and the filter object. In case of UserFilter form, this will be the User and UserFilter objects and in the case of UserGroupFilter form, this will be UserGroup and UserGroupFilter objects. The method that will be called by the framework for constructing the filterform will be createFormControl() where we make use of FormLayout and FormLayoutFormBuilder classes for setting out the layout and for constructing the components. Here is the UserFilterForm class for the User which sets the model and the filter objects as User and UserFilter.
UserFilterForm.java
package net.javabeat.article.spring.rcp.introduction.usermgm.filterform; import net.javabeat.article.spring.rcp.introduction.usermgm.filter.UserFilter; import net.javabeat.article.spring.rcp.introduction.usermgm.model.BaseModel; import net.javabeat.article.spring.rcp.introduction.usermgm.model.User; public class UserFilterForm extends AbstractFilterForm<User, UserFilter>{ public UserFilterForm(String filterFormId){ super(filterFormId); } @Override protected UserFilter newFormObject(){ return new UserFilter(); } protected Class<? extends BaseModel> getModel(){ return User.class; } }
Similarly, the following class sets the model and the filter objects to UserGroup and UserGroupFilter objects.
UserGroupFilterForm.java
package net.javabeat.article.spring.rcp.introduction.usermgm.filterform; import net.javabeat.article.spring.rcp.introduction.usermgm.filter.UserGroupFilter; import net.javabeat.article.spring.rcp.introduction.usermgm.model.BaseModel; import net.javabeat.article.spring.rcp.introduction.usermgm.model.UserGroup; public class UserGroupFilterForm extends AbstractFilterForm<UserGroup, UserGroupFilter>{ public UserGroupFilterForm(String filterFormId){ super(filterFormId); } @Override protected UserGroupFilter newFormObject(){ return new UserGroupFilter(); } protected Class<? extends BaseModel> getModel(){ return UserGroup.class; } }
The following listing shows the configuration for the filterforms in the file filterforms.xml
filterforms.xml
<?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/spring-beans-2.5.xsd"> <bean name="abstractFilterForm" scope="prototype" abstract="true"> </bean> <bean name="userFilterForm" scope="prototype" parent="abstractFilterForm"> <constructor-arg index = "0" value="userFilterForm"/> <property name="filter" ref="userFilter"/> </bean> <bean name="userGroupFilterForm" scope="prototype" parent="abstractFilterForm"> <constructor-arg index = "0" value="userGroupFilterForm"/> <property name="filter" ref="userGroupFilter"/> </bean> </beans>
Data Provider Components
Data Provider components in Spring RCP stand as an interface between the datastore and the CRUD actions that happen in the UI. This component can control whether the data in the UI can be created, updated or delete through supportsCreate()/doCreate(), supportsUpdate()/doUpdate() and supportsDelete()/doDelete() methods. So in the UI, if we are going to allow the user to update the data, then supportUpdate() method must be overridden to return true, in which case doUpdate() method will be called which does the real update of the synchronizing the UI values to the datastore. And for example, if we are not going to allow the users to delete a record, then supportsDelete() method has to be overridden returning false and doDelete() won’t be called at all. The complete listing for the Abstract Data Provider is shown below,
AbstracttDataProvider.java
package net.javabeat.article.spring.rcp.introduction.usermgm.dataprovider; import net.javabeat.article.spring.rcp.introduction.usermgm.model.BaseModel; import net.javabeat.article.spring.rcp.introduction.usermgm.service.AbstractService; import org.springframework.richclient.widget.editor.provider.AbstractDataProvider; import java.util.List; public class AbstracttDataProvider extends AbstractDataProvider{ private AbstractService service; public AbstracttDataProvider(){ } public AbstractService getService(){ return service; } public void setService(AbstractService service){ this.service = service; } public boolean supportsFiltering(){ return true; } public boolean supportsUpdate(){ return true; } public boolean supportsCreate(){ return true; } public boolean supportsClone(){ return false; } public boolean supportsDelete(){ return true; } @SuppressWarnings("unchecked") @Override public Object doCreate(Object newData){ if (newData instanceof BaseModel){ return service.save((M)newData); }else{ return null; } } @SuppressWarnings("unchecked") @Override public void doDelete(Object dataToRemove){ if (dataToRemove instanceof BaseModel){ service.delete((M)dataToRemove); } } @SuppressWarnings("unchecked") @Override public Object doUpdate(Object updatedData){ if (updatedData instanceof BaseModel){ return service.save((M)updatedData); }else{ return null; } } public List<? extends BaseModel> getList(Object criteria){ return null; } }
The following listing provides User object related functionalities for the dataprovider.
UserDataProvider.java
package net.javabeat.article.spring.rcp.introduction.usermgm.dataprovider; import java.util.List; import net.javabeat.article.spring.rcp.introduction.usermgm.filter.UserFilter; import net.javabeat.article.spring.rcp.introduction.usermgm.model.User; import net.javabeat.article.spring.rcp.introduction.usermgm.service.UserService; public class UserDataProvider extends AbstracttDataProvider{ public List getList(Object criteria){ UserService service = getService(); if (criteria instanceof UserFilter){ return (List)service.findModels(((UserFilter)criteria)); }else if (criteria instanceof User){ return (List)service.findModels((UserFilter)new UserFilter().fromModel((User)criteria)); }else{ throw new IllegalArgumentException("This provider can only filter through SupplierFilter, not " + criteria.getClass()); } } public UserService getService(){ return (UserService)super.getService(); } }
Similarly, the following listing shows the data provider implementation for the UserGroup object.
UserGroupDataProvider.java
package net.javabeat.article.spring.rcp.introduction.usermgm.dataprovider; import java.util.List; import net.javabeat.article.spring.rcp.introduction.usermgm.filter.UserGroupFilter; import net.javabeat.article.spring.rcp.introduction.usermgm.model.UserGroup; import net.javabeat.article.spring.rcp.introduction.usermgm.service.UserGroupService; public class UserGroupDataProvider extends AbstracttDataProvider{ public List getList(Object criteria){ UserGroupService service = getService(); if (criteria instanceof UserGroupFilter){ return (List)service.findModels(((UserGroupFilter)criteria)); }else if (criteria instanceof UserGroup){ return (List)service.findModels((UserGroupFilter)new UserGroupFilter().fromModel((UserGroup)criteria)); }else{ throw new IllegalArgumentException("This provider can only filter through SupplierFilter, not " + criteria.getClass()); } } public UserGroupService getService(){ return (UserGroupService)super.getService(); } }
Now we will create a file dataproviders.xml for configuring the above data provider objects.
dataproviders.xml
<?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/spring-beans-2.5.xsd"> <bean name="abstractDataProvider" abstract="true"> </bean> <bean name="userDataProvider" parent = "abstractDataProvider"> <property name="service" ref="userService"/> </bean> <bean name="userGroupDataProvider"> <property name="service" ref="userGroupService"/> </bean> </beans>
Data Editor components
It is this data editor component which is the core for this application and that controls the flow between the model, filter, form and filter form objects. So this data editor component provides a table for displaying all the model objects, a filter form in the right-hand side for filtering the model objects as well as a form object which displays the currently selected model object in the table. Have a look at the following code listing.
AbstractDataEditor.java
package net.javabeat.article.spring.rcp.introduction.usermgm.editor; import net.javabeat.article.spring.rcp.introduction.usermgm.dataprovider.AbstracttDataProvider; import net.javabeat.article.spring.rcp.introduction.usermgm.filter.AbstractFilter; import net.javabeat.article.spring.rcp.introduction.usermgm.filterform.AbstractFilterForm; import net.javabeat.article.spring.rcp.introduction.usermgm.form.AbstractForm; import net.javabeat.article.spring.rcp.introduction.usermgm.model.BaseModel; import org.springframework.richclient.widget.editor.DefaultDataEditorWidget; import org.springframework.richclient.widget.table.PropertyColumnTableDescription; public class AbstractDataEditor> extends DefaultDataEditorWidget{ private AbstractForm abstractForm; private AbstractFilterForm<BaseModel, AbstractFilter> abstractFilterForm; public AbstractDataEditor(String id, AbstracttDataProvider<? extends BaseModel> dataProvider){ setId(id); setDataProvider(dataProvider); init(); } public AbstractForm getAbstractForm() { return abstractForm; } public void setAbstractForm(AbstractForm abstractForm) { this.abstractForm = abstractForm; } public AbstractFilterForm<BaseModel, AbstractFilter> getAbstractFilterForm() { return abstractFilterForm; } public void setAbstractFilterForm(AbstractFilterForm<BaseModel, AbstractFilter> abstractFilterForm) { this.abstractFilterForm = abstractFilterForm; } protected void init(){ setDetailForm(abstractForm); setFilterForm(abstractFilterForm); populateTable(); } protected void populateTable(){ PropertyColumnTableDescription tableDescription = new PropertyColumnTableDescription(getDataEditorName(), getModel()); addColumns(tableDescription); setTableWidget(tableDescription); } protected void addColumns(PropertyColumnTableDescription tableDescription){ tableDescription.addPropertyColumn("id"); tableDescription.addPropertyColumn("name"); } protected String getDataEditorName(){ return getId(); } protected Class<? extends BaseModel> getModel(){ return BaseModel.class; } }
Same goes for the data editor implementation for UserGroup where we have added the column description.
UserGroupDataEditor.java
package net.javabeat.article.spring.rcp.introduction.usermgm.editor; import net.javabeat.article.spring.rcp.introduction.usermgm.dataprovider.UserGroupDataProvider; import net.javabeat.article.spring.rcp.introduction.usermgm.model.BaseModel; import net.javabeat.article.spring.rcp.introduction.usermgm.model.UserGroup; import org.springframework.richclient.widget.table.PropertyColumnTableDescription; public class UserGroupDataEditor extends AbstractDataEditor{ public UserGroupDataEditor(String id, UserGroupDataProvider userGroupDataProvider){ super(id, userGroupDataProvider); } protected void addColumns(PropertyColumnTableDescription tableDescription){ super.addColumns(tableDescription); tableDescription.addPropertyColumn("description"); } protected Class<? extends BaseModel> getModel(){ return UserGroup.class; } }
Now it’s time to wire all the dependant components for the data providers in the spring’s configuration file editors.xml
editors.xml
<?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/spring-beans-2.5.xsd"> <bean name="abstractDataEditor" scope="prototype" abstract="true" init-method="init"> </bean> <bean name="userDataEditor" scope="prototype" init-method="init" parent="abstractDataEditor"> <constructor-arg index = "0" value="userDataEditor"/> <constructor-arg index = "1" ref="userDataProvider"/> <property name="abstractForm" ref="userForm"/> <property name="abstractFilterForm" ref="userFilterForm"/> </bean> <bean name="userGroupDataEditor" scope="prototype" init-method="init" parent="abstractDataEditor"> <constructor-arg index = "0" value="userGroupDataEditor"/> <constructor-arg index = "1" ref="userGroupDataProvider"/> <property name="abstractForm" ref="userGroupForm"/> <property name="abstractFilterForm" ref="userGroupFilterForm"/> </bean> </beans>
Command components
Menu Bar and menu items can be created in Spring RCP using Commands. In essence, a command represents some set of actions to be executed and a visual component can be associated with a command. In our example application, we will create menu bar called User Management and we will have menu items User and User Group. Spring RCP provides CommandGroupFactoryBean for creating menus and tool bars. Have a look at the following commands.xml file.
commands.xml
<?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/spring-beans-2.5.xsd"> <bean id="menuBar"> <property name="members"> <list> <ref bean="userManagementMenu" /> </list> </property> </bean> <bean id="userManagementMenu"> <property name="members"> <list> <ref bean="userCommand"/> <ref bean="userGroupCommand"/> <value>separator</value> <bean /> </list> </property> </bean> <bean id="userCommand"> <property name="widgetViewDescriptorId" value="userView" /> </bean> <bean id="userGroupCommand"> <property name="widgetViewDescriptorId" value="userGroupView" /> </bean> </beans>
In the above configuration file, we have indicated that we are going to create a menu bar object through the spring bean menubar. The list of menu items to be associated with the menu bar is specified through the property members which is containing a reference to userManagementMenu. Note that the class attribute for userManagementMenu is CommandGroupFactoryBean which is used to create the menu items user and user group. The members property for CommandGroupFactoryBean can contain menu items – as specified by references userCommand and userGroupCommand, separator – as specified by the special constant value separator. The members property can also specify the command class like ExitCommand which is a built-in command which when clicked will exit the application.
Note that both the command objects userCommand and userGroupCommand are of type WidgetViewCommand which is a special type of UI command that will display a view. The view to be displayed when the command is executed – i.e when the user clicks the command is specified through the property widgetViewDescriptorId which takes the value userView and userGroupView. These are just the abstract view identifiers, however the original view definition has to be mentioned somewhere which will denote the actual display. In our application this is defined in views.xml file.
views.xml
<?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/spring-beans-2.5.xsd"> <bean name="userView" scope="prototype"> <constructor-arg index="0" value="userView"/> <constructor-arg index="1" ref="userDataEditor"/> <property name="viewClass" value="org.springframework.richclient.widget.WidgetView"/> </bean> <bean name="userGroupView" scope="prototype"> <constructor-arg index="0" value="userGroupView"/> <constructor-arg index="1" ref="userGroupDataEditor"/> <property name="viewClass" value="org.springframework.richclient.widget.WidgetView"/> </bean> </beans>
Note that the view identifiers userView and userGroupView are given idenfiers userView and userGroupView as specified by the first constructor object. The second argument to the constructor is userDataEditor/userGroupDataEditor which represents the original display of the view. In our case, the display happens to be table containing the model objects supported by forms and filter forms.
Till now, we have specified the label of the menu and the menu items. We will be externalizing the label for menu and menu items in a properties file. Have a look at the following snippet,
messages.properties
... ... #Menu userManagementMenu.label = User Management userManagementMenu.caption = User Management #Menu #MenuItems userCommand.label = Users userGroupCommand.label = User Groups #MenuItems The above definitions go into a property file. Note that userManagementMenu, userCommand, userGroupCommandLabel are the bean identifiers we have defined in commands.xml file. Have a look at the following declaration, #User userDataEditor.id.header = Id userDataEditor.name.header = Name userDataEditor.dateOfBirth.header = Date of Birth userDataEditor.userGroup.header = User Group userDataEditor.title = Users userDataEditor.description = List of users userView.title = Users userView.label = Users userForm.id.label = Id userForm.name.label = Name userForm.dateOfBirth.label = Date of Birth userForm.userGroup.label = User Group userFilterForm.nameContains.label = Name contains #User ... ...
In the above snippet we have defined the display name of id, name, date of birth, user group which are the column headers. The id userDataEditor is the identifier specified for the editors.xml file and the properties id, name, dateOfBirth and userGroup are the ones which were specified earlier t hrough the following code in AbstractDataEditor, UserDataEditor and UserGroupDataEditor classes.
tableDescription.addPropertyColumn(propertyName)
The properties title and description of userDataEditor are used to specify the title and description of the data editor view. The remaining definitions declare the display name for user form and user filter forms.
messages.properties
#UserGroup userGroupDataEditor.id.header = Id userGroupDataEditor.name.header = Name userGroupDataEditor.description.header = Description userGroupDataEditor.title = User Groups userGroupDataEditor.description = List of User groups userGroupView.title = User Groups userGroupView.label = User Groups userGroupForm.id.label = Id userGroupForm.name.label = Name userGroupForm.description.label = Description userGroupFilterForm.nameContains.label = Name contains #UserGroup
Spring Rich Client Sample Application
In Spring RCP, an application is represented by the Application class. We will never deal will Application class directory. Instead we will define an Application Runner class that will run the application by reading the configuration information. Have a look at the following code,
UserMgmApp.java
package net.javabeat.article.spring.rcp.introduction.usermgm; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.richclient.application.ApplicationLauncher; public class UserMgmApp{ private static final Log logger = LogFactory.getLog(UserMgmApp.class); public static void main(String[] args) { String rootDirectoryForContext = "/ctx"; String contextPath = rootDirectoryForContext + "/appbundle.xml"; try { new ApplicationLauncher(null, new String[] {contextPath}); } catch (RuntimeException e) { logger.error("RuntimeException during startup", e); } } }
In the above code, we are reading the configuration information from the file appbundle.xml which resides in ctx folder. In the appbundle.xml file, we are going to import or include all the spring beans that we have defined so far. The following listing provides the content of appbundle.xml file.
appbundle.xml
<?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/spring-beans-2.5.xsd"> <import resource="application.xml"/> <import resource="commands.xml"/> <import resource="models.xml"/> <import resource="filters.xml"/> <import resource="filterforms.xml"/> <import resource="editors.xml"/> <import resource="forms.xml"/> <import resource="dataproviders.xml"/> <import resource="binders.xml"/> <import resource="services.xml"/> <import resource="views.xml"/> </beans>
We have included all the configuration files that we have created starting from models, filters, services, data-editors, forms and filter forms. We haven’t yet seen application.xml and the content of which is shown below,
application.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd"> <bean id="application"> <constructor-arg index="0" ref="applicationDescriptor"/> <constructor-arg index="1" ref="lifecycleAdvisor"/> </bean> <bean id="lifecycleAdvisor"> <property name="windowCommandBarDefinitions" value="ctx/commands.xml"/> <property name="windowCommandManagerBeanName" value="windowCommandManager"/> <property name="startingPageId" value="userView"/> <property name="menubarBeanName" value="menuBar"/> <property name="navigationBeanName" value="menuBar"/> <property name="onlyOneExpanded" value="false"/> </bean> <bean id="applicationDescriptor"> <property name="version" value="1.0"/> <property name="buildId" value="20060408-001"/> </bean> <bean id="messageSource"> <property name="basenames"> <list> <value>messages.messages</value> <value>org.springframework.richclient.application.messages</value> </list> </property> </bean> </beans>
By default, the Application Runner will look for the bean identifier application which defines the application’s life-cycle and the application’s descriptor. The life-cycle defines framework for hooking up menu components, command components and other functionalities.The framework will also look for a bean with the identifier messageSource for picking up the messages. We have included the property file message.properties that we have used earlier for setting up the display names for various UI components. The complete source code is included in this article. The class containing the main method is UserMgmApp which on running produces the following.
Screen-shot of User Management application
In this application, provision is given for doing CRUD operations on User and User Group objects.
Conclusion
In this article, we saw how to develop User Management application for managing User and UserGroup objects through Spring RCP framework. It covers the various concepts and components related to Spring RCP like Data Providers, Data Editors, Forms, Filter forms, Views, Application Lifecycle etc.
also read: