The lifecycle of creating an EntityBackingBean:
Call EntityInitializer (if any, before prepareEntity) | Prepare Entity | Call EntityInitializer (if any, after prepareEntity) | Authorize Access (access control) | Build ViewConfig | Build EntityProperty Model | Call EntityInitializer (if any, after building EntityProperty Model) | Build Header/Footer MenusAfter an EntityBackingBean is created, it can be added to a region of root ContainerBean or DialogBean in order to be rendered on client device. With an EntityBackingBean, user can create a new entity, view or modify an existing entity, or perform query/report depending on the persistent state of the entity and current EntityBackingBean's viewType and mode.
For the lifecycle of persisting entity (create a new entity or update an existing entity), see section Transaction Interceptor.
@Entity(name = "Employee")
@Table(name = "Employee")
public class Employee extends PersistenceEntityBase implements NormativeId {
public enum Type {
FULL_TIME,
PART_TIME
}
private String name;
private String nid; // employee id
private Type type;
private Date hiredDate;
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
public Long getId() {
return id;
}
@Column(nullable=false, length=30)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Column(nullable=false, unique=true, length=10)
public String getNid() {
return nid;
}
public void setNid(String nid) {
this.nid = nid;
}
@Column(nullable=false)
@Enumerated(value=EnumType.ORDINAL)
public Type getType() {
return type;
}
public void setType(Type type) {
this.type = type;
}
@Column(nullable=false)
@Temporal(value=TemporalType.DATE)
public Date getHiredDate() {
return hiredDate;
}
public void setHiredDate(Date hiredDate) {
this.hiredDate = hiredDate;
}
public String toString() {
return nid + "/" + name;
}
}
For an entity type to be managed by Cmobilecom AF for
entity creation, edit, view and query, its EntityBackingBean must be defined.
For example,
public class EmployeeBean extends EntityBackingBean<Employee> {
@Properties({
@Property(name="nid", view={ViewType.ALL}),
@Property(name="name", view={ViewType.ALL}),
@Property(name="hiredDate", view={ViewType.ALL}),
@Property(name="type", view={ViewType.ALL},
renderStyle=@RenderStyleDef(style=RenderStyle.SELECT_ONE_MENU))
})
@Override
public boolean hasPropertyAnnotations() {
return true;
}
@Override
public List<SelectItem> getPropertySelectItems(
EntityProperty<Employee> property) throws SystemException {
ChoiceType choiceType;
String propertyName = property.getName();
if (propertyName.equals("type"))
choiceType = HrModule.EMPLOYEE_TYPE;
else
return super.getPropertySelectItems(property);
return SelectItemListProvider.getInstance().
getSelectItems(choiceType, property);
}
}
EntityBackingBean has built-in functionalities of entity persistence,
query, editing and presentation using entity and property annotations.
ViewConfig / \ PersistenceDataViewConfig MenuViewConfig / \ EntityViewConfig EntityListViewConfigAn EntityViewConfig can be provided when creating an EntityBackingBean, and it can be updated during bean initialization.
@Override
protected EntityViewConfig buildViewConfig(T entity) throws SystemException {
EntityViewConfig viewConfig = super.buildViewConfig(entity);
viewConfig.setPropertiesToHide(...);
return viewConfig;
}
An EntityListViewConfig can be provided when creating an EntityListBackingBean, and
it can be updated during bean initialization.
@Override
protected EntityListViewConfig buildEntityListViewConfig(EntityListBackingBean<T> entityListBackingBean,
EntityListViewConfig viewConfig) throws SystemException {
viewConfig = super.buildEntityListViewConfig(entityListBackingBean, viewConfig);
viewConfig.setPropertiesToShow(...);
return viewConfig;
}
The list of properties shown on entity view can be configured in View Config.
Custom properties can be annotated in the same way as regular property annotations (custom=true), or they can be added dynamically after entity property model is built.@Override protected void postRefreshEntityPropertyModel(T entity) throws SystemException { super.postRefreshEntityPropertyModel(entity); List<EntityProperty<T>> entityPropertyList = getEntityPropertyList(); EntityProperty<T> fooProperty = new EntityProperty(this, "foo", new RenderStyle(RenderStyle.INPUT_TEXT); fooProperty.setCustomProperty(true); entityPropertyList.add(fooProperty); }
More menu nodes can be added if needed. The following example, add menu node(Approve) in view mode.
private static final String COMMAND_APPROVE = "Approve";
@Override
protected void addMenuNodes(ModeType modeType) throws SystemException {
super.addMenuNodes(modeType);
if (modeType.equals(ModeType.VIEW)) {
AccessControlAccessorWrapper acAccessor = getAccessControlAccessor();
User currentUser = this.getAuthenticatedUser();
if (acAccessor.isUserHasPermission(currentUser, "HR", "ApproveEC")) {
MenuBean menuBean = getMenuBean(COMMAND_APPROVE, false);
if (menuBean != null) {
MenuNode approveMenuNode = new MenuNode(menuBean,
COMMAND_APPROVE, RenderStyle.COMMAND_BUTTON);
approveMenuNode.setIcon(MenuNode.UI_ICON_CHECK);
menuBean.add(approveMenuNode);
}
}
}
}
Override clickMenuNode() to handle menu node actions.
@Override
public PageNavigation clickMenuNode(MenuNode menuNode) throws SystemException {
String command = menuNode.getCommand();
if (command.equals(COMMAND_APPROVE)) {
// handle "Approve" command
return null;
}
return super.clickMenuNode(menuNode);
}
Override header or footer MenuViewConfig to show/hide commands
and specify which menu nodes are shown in header or footer menu.
@Override
protected MenuViewConfig getHeaderMenuViewConfig() {
MenuViewConfig viewConfig = MenuViewConfig(MenuViewConfig.MenuStyle.MENU_BAR, RenderStyle.COMMAND_LINK, true);
// show some menu nodes
viewConfig.setMenuNodesToShow(...);
viewConfig.addMenuNodesToShow(...);
//or hide some menu nodes
viewConfig.setMenuNodesToHide(...);
viewConfig.addMenuNodesToHide(...);
return viewConfig;
}
@Override
protected MenuViewConfig getFooterMenuViewConfig() {
MenuViewConfig viewConfig = MenuViewConfig(MenuViewConfig.MenuStyle.MENU_BAR, RenderStyle.COMMAND_BUTTON, false);
// show some menu nodes
viewConfig.setMenuNodesToShow(...);
viewConfig.addMenuNodesToShow(...);
//or hide some menu nodes
viewConfig.setMenuNodesToHide(...);
viewConfig.addMenuNodesToHide(...);
return viewConfig;
}
Another way to specify whether a command is supported,
override the isCommandSupported(...) method. If a command is not supported,
it will not be in header or footer menu.
@Override
protected boolean isCommandSupported(PersistenceDataBackingBean<T> backingBean, String command) throws SystemException {
}
The nested ViewConfig name for header menu is "headerMenu", and the nested ViewConfig name for footer menu
is "footerMenu". And they can also be accessed by:
MenuViewConfig headerMenuViewConfig = entityViewConfig.getHeaderMenuViewConfig(true);
MenuViewConfig footerMenuViewConfig = entityViewConfig.getFooterMenuViewConfig(true);
So they can be configured in buildViewConfig().
@Override
public void addContextMenuNodes(TypedMenuNodeContextMenuBean menuBean) throws SystemException {
super.addContextMenuNodes(menuBean);
// add more menu nodes
}
Override clickContextMenuNode() to handle context menu node actions:
@Override
public PageNavigation clickContextMenuNode(MenuNode menuNode) throws SystemException {
String command = menuNode.getCommand();
if (command.equals("aCommand")) {
// handle action
}
return super.clickContextMenuNode(menuNode);
}
@Override
public PageNavigation handlePartialBehaviorEvent(PartialBehaviorEvent event,
PersistenceDataBackingBean<T> backingBean,
T entity, EntityProperty<T> property) throws SystemException {
}
For example, set Employee default name when its associated user is set or changed.
public class EmployeeBean extends EntityFormSupportBean<Employee> {
@Override
public PageNavigation handlePartialBehaviorEvent(PartialBehaviorEvent event, PersistenceDataBackingBean<Employee> backingBean,
Employee entity, EntityProperty<Employee> property) throws SystemException{
String propertyName = property.getName();
if (propertyName.equals(Employee.PROPERTY_USER)) {
// set employee name
User user = entity.getUser();
if (user != null) {
entity.setName(user.getName());
// add name as a render target
addPropertyRenderTarget(Employee.PROPERTY_NAME, entity);
}
return null;
}
return super.handlePartialBehaviorEvent(event, backingBean, entity, property);
}
}
ExpenseClaimItem list bean: update expense total when an item expense is changed.
public class ExpenseClaimItemBean extends EntityBackingBean<ExpenseClaimItem> {
@Override
public PageNavigation handlePartialBehaviorEvent(PartialBehaviorEvent event,
PersistenceDataBackingBean<ExpenseClaimItem> backingBean,
ExpenseClaimItem entity, EntityProperty<ExpenseClaimItem> property) throws SystemException{
String propertyName = property.getName();
if (propertyName.equals(ExpenseClaimItem.PROPERTY_EXPENSE) &&
event.getPartialBehavior().isValueChange()) {
// update statisticsRow: expense total (render target)
((EntityListBackingBean<ExpenseClaimItem>)backingBean).
refreshStatisticsEntityPropertyValue(property);
return null;
}
return super.handlePartialBehaviorEvent(event, backingBean, entity, property);
}
}
A menu node can have an EntityBackingBean for user to input data as arguments for the command,
and the input data EntityBackingBean supports partial behaviors for its properties. Override
the following method to handle property PartialBehaviorEvent(s) of a menu node's input data bean.
@Override
public <M extends PersistenceEntity> PageNavigation handlePartialBehaviorEvent(
PartialBehaviorEvent event, MenuNode menuNode, EntityBackingBean<M> inputDataBackingBean,
EntityProperty<M> property) throws SystemException {
}
@Override
protected EntityFetchGraph<ExpenseClaim> getEntityFetchGraph(ExpenseClaim entity) {
EntityFetchGraph<ExpenseClaim> fetchGraph = super.getEntityFetchGraph(entity);
fetchGraph.addProperty("expenseClaimItems", JoinType.LEFT);
fetchGraph.addPropertyPath("employee.address", JoinType.LEFT);
return fetchGraph;
}
To define a fetch graph for showing a list of ExpenseClaim(s), override the following method
in its EntityBackingBean:
@Override
protected EntityFetchGraph<ExpenseClaim> getEntityFetchGraph(EntityListBackingBean<xpenseClaim> entityListBackingBean) {
EntityFetchGraph<ExpenseClaim> fetchGraph = super.getEntityFetchGraph(entityListBackingBean);
fetchGraph.addProperty("employee", JoinType.INNER);
return fetchGraph;
}
@Properties({
@Property(name=ExpenseClaim.PROPERTY_EXPENSE_CLAIM_ITEMS,
entityPropertyType=EntityFormBeanProperty.class,
nullAsDeleteProperties={ExpenseClaimItem.PROPERTY_DATE},
view={ViewType.ENTITY})
})
A FormBean viewConfig can be set as a nested ViewConfig of its parent bean's ViewConfig.
viewConfig.setFormBeanViewConfig(formBeanPropertyName, formBeanViewConfig);
Get property select items that is static and will be cached.
@Override
public List<SelectItem> getPropertySelectItems(EntityProperty<T> property) throws SystemException {
String propertyName = property.getName();
if (propertyName.equals("type")) {
...
}
return super.getPropertySelectItems(property);
}
Get property dynamic select items that will not be cached.
@Properties({
@Property(name="type", view={ViewType.ALL},
renderStyle=@RenderStyleDef(style=RenderStyle.SELECT_ONE_MENU, dynamicSelectItems=true))
})
@Override
public List<SelectItem> getDynamicSelectItems(PersistenceDataBackingBean<T> backingBean,
T entity, EntityProperty<T> property) throws SystemException {
String propertyName = property.getName();
if (propertyName.equals("type")) {
...
}
return super.getDynamicSelectItems(backingBean, entity, property);
}
Note that the values of SelectItems must be of String type. The string value will be converted to its property type when property value is updated(Model Update). Refer to SelectItemListProvider class for methods to create SelectItems from various data values.
SelectItem labels are of type I18NName, and should not be localized. The labels will be translated using resource bundles during render phase.
@Properties({
@Property(name="type", view={ViewType.ALL},
dynamicRenderStyle=true,
renderStyle=@RenderStyleDef(style=RenderStyle.SELECT_ONE_MENU))
})
@Override
public RenderStyle getDynamicRenderStyle(PersistenceDataBackingBean<T> backingBean,
T entity, EntityProperty<T> property) throws SystemException {
String propertyName = property.getName();
if (propertyName.equals("type")) {
...
}
return super.getDynamicRenderStyle(backingBean, entity, property);
}
@Properties({
@Property(name="type", view={ViewType.ALL},
dynamicValue=true)
})
@Override
public Object getPropertyDynamicValue(PersistenceDataBackingBean<T> backingBean,
T entity, EntityProperty<T> property) throws SystemException {
String propertyName = property.getName();
if (propertyName.equals("type")) {
...
}
return super.getPropertyDynamicValue(backingBean, entity, property);
}
@Override
public void validate() throws SystemException {
super.validate();
...
}
In query view, the validate() method is also called before search.
See Validation.
Lifecycle of creating entities on persistence.
Begin Transaction | Validate Entity | Access Control (check permission) | prePersist (called before added to PersistentContext) | Persist Entity (added to PersistentContext) | postPersist (called after added to PersistentContext) | Commit Transaction | Call Transaction Listeners (called after transaction is committed)Add business logic in validate(), prePersist(), postPersist() and/or transaction listeners. All the changes to any entities in the lifecycle methods before commit will be in the same transaction. There can be many entities created on persistence due to persist cascade and other application logic. Transaction listeners are used to refresh views after commit.
preCreate: called before the entity is added to PersistenceContext. entity id is not available at this point if it has an id generator.
protected boolean preCreate(T entity, ActionDescriptor actionDescriptor,
PersistenceEntityManager peManager) throws SystemException {
}
postCreate: called after the entity is added to PersistenceContext and entity id is generated if it has an id generator.
protected void postCreate(T entity, ActionDescriptor actionDescriptor,
PersistenceEntityManager peManager)throws SystemException {
}
Lifecycle of updating entities on persistence.
Begin Transaction | Validate Entity | preMerge (called before merge) | Merge Entity (added to PersistenceContext) | postMerge (called after merge) | Commit Transaction | Call Transaction Listeners (called after transaction is committed)
There is no access control in the lifecycle because the EntityBackingBean is enabled for editing only when the user has the permission to do so. That is, access control is enforced before the lifecycle starts.
Add business logic in validate(), preMerge(), postMerge() and/or transaction listeners. All the changes to any entities in the lifecycle methods before commit will be in the same transaction. There can be many entities merged or created on persistence due to merge cascade and other application logic. Transaction listeners are used to refresh views after commit.
preMerge: called before entity is merged to PersistentContext.
protected T preMerge(T entity, ActionDescriptor actionDescriptor,
PersistenceEntityManager peManager)throws SystemException {
}
postMerge: called after entity is merged to PersistentContext.
protected void postMerge(T entity, ActionDescriptor actionDescriptor,
PersistenceEntityManager peManager) throws SystemException {
}
Lifecycle of deleting entities on persistence.
Begin Transaction | Access Control | preRemove (called before remove) | Remove Entity (remove entity in Persistent Context) | postRemove (called after remove) | Commit Transaction | Call Transaction Listeners (called after transaction is committed)
Add business logic in preRemove(), postRemove() and/or transaction listeners. All the changes to any entities in the lifecycle methods before commit will be in the same transaction. There can be many entities deleted on persistence due to remove cascade and other application logic. Transaction listeners are used to refresh views after commit.
preRemove: called before removing the entity in persistent context.
@Override
protected boolean preRemove(T entity, ActionDescriptor actionDescriptor,
PersistenceEntityManager peManager)throws SystemException {
}
postRemove: called after the entity is removed in persistent context.
@Override
protected void postRemove(T entity, ActionDescriptor actionDescriptor,
PersistenceEntityManager peManager)throws SystemException {
}
@Override
public StatisticsProperty[] getStatisticsProperties() {
return new StatisticsProperty[]{
new StatisticsProperty(ExpenseClaimItem.PROPERTY_EXPENSE, Aggregate.SUM)
};
}
Entity bean layout type can be GRID_NAME_VALUE, LAYOUT_CODE, TABLE, FLOW, TAB_VIEW, LIST_VIEW, COLUMN_VIEW or GRID. The default layout is GRID_NAME_VALUE with property name column and property value column.
For tab view, top-level properties should be property groups or FormBean properties. For layout code, form designs allow end users to customize/create layouts. But if form design is not supported, override getLayoutCode() to provide the layout code. Layout code is a piece of HTML code that can embed expressions. For example,
@Override
public String getLayoutCode() throws SystemException {
return "<h1>Expense Claim</h1> +
"#{bundle.Employee}: #{employee}<br/>" +
"#{OBJECT:expenseClaimItems}
" +
"#{creator} #{createdDate}" +
}
public class DepartmentBean extends HierarchyEntityBackingBean<Department> { @Override public <R extends PersistenceEntity> EntityNameResolver<Department, R> getEntityNameResolver() { return new EntityNameResolver<Department, R>() { @Override public I18NName getEntityName(Department department, EntityProperty<R> entityProperty) { return new I18NName(department.getName() + "(" + department.getManager() + ")", true); } }; } }
Deleting all the entities of all pages can be a long time task, so progress bar is automatically enabled.
A default progress bar will be enabled if the menu node is set to show progress bar.
MenuNode menuNode = new MenuNode(...);
menuNode.setShowProgress(true);
The following method can be overridden to customize progress bar. Return null if
progress bar is disabled for the menu node.
@Override
public ProgressDescriptor getProgressDescriptor(PersistenceDataBackingBean<T> backingBean,
MenuNode menuNode) throws SystemException {
}