Mediator Pattern: Reducing Page Dependencies in JSF
Join the DZone community and get the full member experience.
Join For FreeUsing the mediator pattern gives you the ability to develop JSF pages as fine-grained components in your application, allowing them to be tied together clearly and easily.
Mediator Pattern
Usually a web
application is made up of a (sometimes large) number of pages with a number of
underlying classes (backing beans, etc.). So the different functionality is
distributed among these pages. However, as more pages are developed in a
program, especially during maintenance and/or refactoring, the problem of
communication between these pages and underlying classes may become more
complex. This makes the program harder to read and maintain. Furthermore, it
can become difficult to change the program, since any change may affect code in
several other classes.
Sample Application
The example application is made up from the several pages. The first represents list of the clients. The client can be a private person or a company.
The client
can be added by clicking on the “Add Person” or on the “Add Company” link. On
the next step the page with editable details is opened:
or
Next, the
user should select address for client on the separate form with the list of
addresses:
After he or
she selected address, other details should be added and client can be added
with “Add” button.
The example
application is quite simple and its only purpose is to show the usage of the
pattern.
Applying Pattern
User sessionThere are
two commonly used ways to organize interaction between pages in JSF: via
session scope or request. The example introduced in the article is based on the
first approach and uses session scope bean as a session:
public class UserSession {
private Map<String, Object> sessionMap = new HashMap<String, Object>();
public synchronized void setAttribute(String key, Object value){
if(value == null) {
sessionMap.remove(key);
} else {
sessionMap.put(key, value);
}
}
public synchronized Object getAttribute(String key){
return sessionMap.get(key);
}
}
It is
defined in the JSF configuration file as follows:
<managed-bean>
<managed-bean-name>userSession</managed-bean-name>
<managed-bean-class>example.session.UserSession</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
Page’s
backing bean uses the Service that retrieves all needed data for the page.
Let’s discover the page displaying the client list as the example. Here is the
part of the JSP page that displays client table:
<h:dataTable value="#{clientsBB.clients}" var="client" style="border:solid 1px gray">
<h:column >
<f:facet name="header"><h:outputText value="Client Name"/></f:facet>
<h:outputText value="#{client.name}"/>
</h:column>
<h:column>
<f:facet name="header"><h:outputText value="Client Address"/></f:facet>
<h:outputText value="#{client.address}"/>
</h:column>
</h:dataTable>
The page
gets data from its backing bean’s getClientsMethod:
public class ClientsBB {
private ClientsService clientsService;
public List<ClientListItem> getClients() {
return clientsService.getClients();
}
public void setClientsService(final ClientsService clientsService) {
this.clientsService = clientsService;
}
}
ClientsService implements business logic of the page. It
retrieves data from data source (or from user session) and composes objects to
be displayed:
public class ClientsService {
public List<ClientListItem> getClients() {
List<ClientListItem> result = new ArrayList<ClientListItem>();
List<Company> companies = DataSource.getCompanies();
for(Company company: companies) {
result.add(ClientListItem.valueOf(company));
}
List<Person> persons = DataSource.getPersons();
for(Person person: persons) {
result.add(ClientListItem.valueOf(person));
}
return result;
}
}
The
managed-beans are defined with the session scope:
<managed-bean>
<managed-bean-name>clientsBB</managed-bean-name>
<managed-bean-class>example.backingbean.ClientsBB</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>clientsService</property-name>
<value>#{clientsService}</value>
</managed-property>
</managed-bean>
<managed-bean>
<managed-bean-name>clientsService</managed-bean-name>
<managed-bean-class>example.service.ClientsService</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
All other pages have the similar behavior.
Mediator
In order to
use “Select Address” page for both “Add Person” and “Add Company” pages it is
desirable to keep it independent on the latter ones. Also if assume using
another page (strategy) for selecting address it is desirable to keep “Add
Person” and “Add Company” pages independent on this “Select Address” page. This
is where the Mediator pattern comes up. The next diagrams show the role of the
mediator object:
Mediator
object is implemented as managed-bean. “Select address” button has an attached action
listener that points to method of the Mediator object. This method tunes up
“Select Address” page behavior using the Service which stores preferences data
in session. Mediator also adds listener that should be fired when address is
selected. The code below will help better understanding of the action flow.
The “select
address” button is defined inside of the page as follows:
<h:commandButton immediate="true"
action="#{selectAddressForPersonBB.selectAddress}"
value="Select Address"/>
Below is
the code of the Mediator class:
public class SelectAddressForPersonBB {
private SelectAddressService selectAddressService;
public String selectAddress() {
// Reset selection for "select address page"
selectAddressService.setSelectedAddress(null);
// Set navigation outcome
selectAddressService.setSuccessNavigationOutcome("personAddressSelected");
// Add listener
selectAddressService.setAddressSelectedListener(new Listener() {
public void fire(FacesContext facesContext) {
Application app = facesContext.getApplication();
SelectAddressService selectAddressService =
(SelectAddressService) app.evaluateExpressionGet(facesContext, "#{selectAddressService}", SelectAddressService.class);
PersonService personService =
(PersonService) app.evaluateExpressionGet(facesContext, "#{personService}", PersonService.class);
Address address = selectAddressService.getSelectedAddress();
// Set person's address
personService.setPersonAddress(address);
// Remove listener
selectAddressService.setAddressSelectedListener(null);
}
});
return "selectAddress";
}
public void setSelectAddressService(final SelectAddressService selectAddressService) {
this.selectAddressService = selectAddressService;
}
}
It is
configured as a backing bean (note: all managed beans, except for the userSession, are
defined with the request scope):
<managed-bean>
<managed-bean-name>selectAddressForPersonBB</managed-bean-name>
<managed-bean-class>example.backingbean.SelectAddressForPersonBB</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>selectAddressService</property-name>
<value>#{selectAddressService}</value>
</managed-property>
</managed-bean>
The Service
of the “Select Address” page stores preferences and listener in session. The
service itself defines interface for tuning the page’s behavior (e.g. it gives
the ability to set the success navigation outcome).
public class SelectAddressService {
private static final String SELECTED_ADDRESS = "selected_address";
private static final String SUCCESS_OUTCOME = "success_outcome";
private static final String SELECTED_ LISTENER = "address_selected_listener";
private UserSession userSession;
public List<Address> getAddresses() {
return DataSource.getAddresses();
}
public void setAddressSelectedListener(Listener listener) {
userSession.setAttribute(SELECTED_LISTENER, listener);
}
public void setSelectedAddress(Address address) {
userSession.setAttribute(SELECTED_ADDRESS, address);
Object listener = userSession.getAttribute(SELECTED_LISTENER);
if(listener != null) {
((Listener) listener).fire(FacesContext.getCurrentInstance());
}
}
public Address getSelectedAddress() {
Object address = userSession.getAttribute(SELECTED_ADDRESS);
return address == null ? null : (Address) address;
}
public void setSuccessNavigationOutcome(String navigationOutcome) {
userSession.setAttribute(SUCCESS_OUTCOME, navigationOutcome);
}
public String getSuccessNavigationOutcome() {
Object navigationOutcome = userSession.getAttribute(SUCCESS_OUTCOME);
return navigationOutcome == null ? null : (String) navigationOutcome;
}
public void setUserSession(final UserSession userSession) {
this.userSession = userSession;
}
}
The diagram
below explains what happens when user selects address on the “Select Address”
page:
After
address is selected it is stored and listener that was added by mediator is
invoked. This listener retrieves selected address and sets it to the person
using the corresponding Service:
public void fire(FacesContext facesContext) {
Application app = facesContext.getApplication();
SelectAddressService selectAddressService =
(SelectAddressService) app.evaluateExpressionGet(facesContext, "#{selectAddressService}", SelectAddressService.class);
CompanyService companyService =
(CompanyService) app.evaluateExpressionGet(facesContext, "#{companyService}", CompanyService.class);
Address address = selectAddressService.getSelectedAddress();
// Set company's address
companyService.setCompanyAddress(address);
// Remove listener
selectAddressService.setAddressSelectedListener(null);
}
Conclusion
The article explored an example of applying the well-known Mediator pattern for Web application development. The examples provided in this article, along with the source code, can help you implement this pattern in almost any JSF-based application.
You are free to download sources http://java.dzone.com/sites/all/files/mediator_in_jsf_app-src.zip
Opinions expressed by DZone contributors are their own.
Trending
-
Clear Details on Java Collection ‘Clear()’ API
-
How To Scan and Validate Image Uploads in Java
-
What Is React? A Complete Guide
-
How to Optimize CPU Performance Through Isolation and System Tuning
Comments