Cambridge Technology PartnersCommunity Documentation
In the first chapters we have laid a solid ground for querying entities.
Connecting those entities to a user interface can be a tedious task -
implementing common CRUD operations often include similar but still
repetitive boilerplate code. CDI Query supports you here with the
EntityHome
API.
EntityHome
requires a JPA 2 metamodel of the entities
used with it. The metamodel can be generated with tools from
Hibernate or
EclipseLink,
or typically almost any JPA 2 persistence provider of your choice.
A typical EntityHome
looks like the following:
@Named
@Stateful
@ConversationScoped
public class BookHome extends EntityHome<Book, Long> {
@Inject
private BookDao bookDao;
@Override
protected EntityDao<Book, Long> getEntityDao() {
return bookDao;
}
@Override
protected List<SingularAttribute<Book, ?>> searchAttributes() {
return singularAttributes()
.addIfNotEmpty(getSearch().getName(), Book_.name)
.getAttributes();
}
}
As you see, there is nothing more than overriding two simple methods:
EntityDao getEntityDao()
returns an instance of a DAO
extending the EntityDao
interface. The
EntityDao
provides all necessary methods
for CRUD operations and pagination.
List searchAttributes()
returns a list of attributes
that a search screen provides and the user has entered to lookup
entities. Note that this requires a JPA 2 metamodel to be available.
A utility API supports creating the list of search attributes with fluent calls to a helper class.
Diving into the EntityHome
class itself gives some
insights in how it will connect to the frontend:
public abstract class EntityHome<E, PK> {
// ACCESSORS AND MUTATORS
public PK getId() { ... }
public void setId(PK id) { ... }
public E getEntity() { ... }
public E getSearch() { ... }
// METHODS
public Object create() { ... }
public void retrieve() { ... }
public Object update() { ... }
public Object delete() { ... }
// PAGINATION
public void search() { ... }
public void paginate() { ... }
public int getPage() { ...}
public void setPage(int page) { ... }
public long getCount() { ... }
public int getPageSize() { ... }
public List<E> getPageItems() { ... }
}
All Home instances are able to store following attributes:
Name | Description |
---|---|
id |
PK id stores the primary key of the entity. This can be
used with a page parameter identifying e.g. the entity to view, update or delete.
|
entity |
E entity holds an entity instance, e.g. on edit or
create screens. The entity can be bound to UI components and getting populated
with user input.
|
search |
E search holds also entity instance, but mainly to hold search
parameters for querying page items. Changing to a create screen is assigning
the search entity to the entity attribute to prepopulate the screen.
This entity is never null .
|
In order to populate attributes and to actually do something with the data, following methods
are available in the EntityHome
class:
Name | Description |
---|---|
create |
create() initializes a conversation. This is not necessarily
a JSF conversation, this is a UI specific concern.
|
retrieve |
retrieve() checks whether an id has been provided
and retrieves the corresponding entity. Otherwise, the entity
attribute is populated with the search entity.
|
update |
update() persists the current entity . Also used
for new entities. It also ends the current conversation.
|
delete |
delete() deletes an entity identified by the id
attribute. It also ends the current conversation.
|
Note that the return parameters are of type Object
(resp. null
by default) or void.
This is overridden by specific UI framework modules, which we will show in the next chapter.
Other attributes and methods are concerned with entity search and pagination through the search results:
Name | Description |
---|---|
| |
page |
int page holds the current page the user is on with regards to the
current search result.
|
pageSize |
int pageSize corresponds to the max items displayed per page. This
defaults to 10.
|
count |
long count counts the current number of search result entities.
|
pageItems |
List pageItems contains all entities which the current page holds.
This is limited to pageSize items.
|
| |
search |
void search() initializes the search screen, which is just resetting
the current page to 0.
|
paginate |
void paginate() does the lookup of the page items for the current page,
also with recalculating the search result size.
|
In order to keep the CDI query core module free from any UI specific code, we have separated these concerns into dedicated modules. Currently only JSF is supported.
To install EntityHome
for JSF, add following dependency
to your POM:
<dependency>
<groupId>com.ctp.cdi.query</groupId>
<artifactId>cdi-query-faces</artifactId>
<version>${cdi.query.version}</version>
<scope>runtime</scope>
</dependency>
Once the JSF module JAR file is part of the runtime classpath, the JSF specific
beans also have to be activated to override the default beans part of the core
module. This can be done by updating your projects beans.xml
with the following alternatives:
<alternatives>
<stereotype>com.ctp.cdi.query.home.FacesHome</stereotype>
</alternatives>
JSF uses mainly String
outcomes to determine navigation rules.
Following methods produce these outcomes:
Method | Outcome | Description |
---|---|---|
create() | create?faces-redirect=true |
Redirects to a create page.
Starts a new conversation (javax.enterprise.context.Conversation ).
|
update() | search?faces-redirect=true | Redirects to a search page when the entity has been created. Ends the current conversation. |
view?faces-redirect=true&id=PK | Redirects to a page the entity can be viewed after updating it. Ends the current conversation. | |
null |
Returns null after an exception has occurred.
Ends the current conversation.
| |
delete() | search?faces-redirect=true | Redirects to a search page when the entity has been deleted. Ends the current conversation. |
Returns null after an exception has occurred.
Ends the current conversation.
|
Typically CRUD screens require three XHTML facelet views, which might look like the following snippets.
search.xhtml
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
template="...">
<!-- Reading URL parameters and reparing the page -->
<f:metadata>
<f:viewParam name="page" value="#{bookHome.page}"/>
<f:event type="preRenderView" listener="#{bookHome.paginate}"/>
</f:metadata>
<!-- Search form sample -->
<h:inputText id="searchName" value="#{bookHome.search.name}"/>
<!-- Commands -->
<h:commandLink value="Search" action="#{bookHome.search}"/>
<h:commandLink value="Create New" action="#{bookHome.create}"/>
<!-- Show search result -->
<h:dataTable id="pageItems" value="#{bookHome.pageItems}" var="item">
<h:column>
<h:link outcome="/.../view">
<f:param name="id" value="#{item.id}"/>
<h:outputText id="itemName" value="#{item.name}"/>
</h:link>
</h:column>
</h:dataTable>
<!-- Pagination -->
<h:panelGroup rendered="#{bookHome.count gt bookHome.pageSize}">
<h:commandLink rendered="#{bookHome.page gt 0}">
<f:param name="page" value="#{bookHome.page - 1}"/>
Previous
</h:commandLink>
<h:commandLink rendered="#{(bookHome.page + 1) * bookHome.pageSize lt bookHome.count}">
<f:param name="page" value="#{bookHome.page + 1}"/>
Next
</h:commandLink>
</h:panelGroup>
<ui:composition>
create.xhtml
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
template="...">
<!-- Reading URL parameters and initializing the page -->
<f:metadata>
<f:viewParam name="id" value="#{bookHome.id}" converter="javax.faces.Long"/>
<f:event type="preRenderView" listener="#{bookHome.retrieve}"/>
</f:metadata>
<!-- Input Values -->
<h:inputText id="bookName" value="#{bookHome.entity.name}"/>
<!-- Commands -->
<h:commandLink value="Save" action="#{bookHome.update}"/>
<!-- Existing Entity -->
<h:link value="Cancel" outcome="view">
<f:param name="id" value="#{bookHome.id}"/>
</h:link>
<h:commandLink value="Delete" action="#{bookHome.delete}"/>
<ui:composition>
view.xhtml
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
template="...">
<!-- Reading URL parameters and initializing the page -->
<f:metadata>
<f:viewParam name="id" value="#{bookHome.id}" converter="javax.faces.Long"/>
<f:event type="preRenderView" listener="#{bookHome.retrieve}"/>
</f:metadata>
<!-- Display the entity -->
<ui:composition>
Note the converters in the <f:viewParam converter="javax.faces.Long" />
tags. The explicit converters are required due to Java type erasure - JSF cannot automatically detect the target
property type. If you leave out the converter, the id
property will not be populated.
Select the converter according to your id
type.