Cmobilecom AF 5.19 Developer Guide

7.5 Entity Form Support

An entity can have one or more properties whose values are single entity or a list of entities. For example, an Employee has one home Address, an ExpenseClaim has a list of ExpenseClaimItems. If the values of the properties are shown as BackingBean(s) inside their enclosing bean, then these properties are called Form Bean Properties.

Take the Example HR module for instance. An Employee has a home address and a mailing address. The following code is the O/R mappings between Employee and Address entities:


@Entity(name="Employee")
@Table(name="Employee")
public class Employee extends PersistenceEntityBase implements NormativeId {
	private Address homeAddress;
	private Address mailingAddress;
  
	@OneToOne
	@JoinColumn(name="homeAddressId")
	public Address getHomeAddress() {
    	return homeAddress;
	}

	public void setHomeAddress(Address homeAddress) {
		this.homeAddress = homeAddress;
	}

	@OneToOne
	@JoinColumn(name="mailingAddressId")
	public Address getMailingAddress() {
		return mailingAddress;
	}

	public void setMailingAddress(Address mailingAddress) {
		this.mailingAddress = mailingAddress;
	}
}

@Entity(name="Address")
@Table(name="Address")
public class Address extends PersistenceEntityBase implements EntityChoiceSupport, ActiveAware {

	private Employee employee;
	private String choice;

	@Override
	public void copyFromSameAsEntity(EntityChoiceSupport entity) {
		...
	}

	@Transient
	@Override
	public String getChoice() {
		return choice;
	}

	public void setChoice(String choice) {
		this.choice = choice;
	}
	
	@ManyToOne
	@JoinColumn(name="employeeId")
	public Employee getEmployee() {
		return employee;
	}

	public void setEmployee(Employee employee) {
		this.employee = employee;
	}

}
Address implements EntityChoiceSupport. In addition to being created in place inside Employee bean, home and mailing addresses can be selected from the employee's existing addresses. When creating an employee, no addresses are available for selection for the employee. But mailing address can be set as the same as home address.

EntityFormSupportBean

EntityFormSupportBean supports one or more entity form bean properties, and takes care of creating and merging form entities in persistence. For example, EmployeeBean:

public class EmployeeBean extends EntityFormSupportBean<Employee> {

}

Form Bean Properties

The values of form bean properties can be an entity or entity list, and they must be annotated or created dynamically. For example, home and mail addresses are entity form bean properties.

	// list home address before mailing address since mailing address
	// can be the same as home address
    @Property(name=Employee.PROPERTY_HOME_ADDRESS, view={ViewType.ENTITY}, 
     		entityPropertyType=EntityFormBeanProperty.class),  
     		
    @Property(name=Employee.PROPERTY_MAILING_ADDRESS, view={ViewType.ENTITY}, 
     		entityPropertyType=EntityFormBeanProperty.class)

Same As Another Property

If the value of an entity form bean property can be the same as another property, tell the framework about it so that a choice will be provided for this purpose. For example, mailing address can be same as home address for an employee.

	@Override
	protected List<String> canFormEntityBeSameAs(Employee entity, String entityFormProperty) {
		if (entityFormProperty.equals(Employee.PROPERTY_MAILING_ADDRESS))
			return Employee.PROPERTY_HOME_ADDRESS;
		
		return null;
	}

Entity Choice Select Query

If a form entity type implements EntityChoiceSupport, then its value can be selected from existing entities. A query criteria is needed to build query criteria for entity selections. For example, Employee home and mailing address can be selected from the employee's existing addresses if the employee has been persisted.

	@Override
	protected List<CriteriaElement> getChoicePropertyQueryElements(Employee entity, 
  		EntityFormBeanProperty<Employee> property) throws SystemException {
		
		String propertyName = property.getName();
		if (propertyName.equals(Employee.PROPERTY_HOME_ADDRESS) ||
			propertyName.equals(Employee.PROPERTY_MAILING_ADDRESS)) {
			if (isCreating()) // employee not created, no choice
				return null;

			// employee addresses for selection
			List<CriteriaElement> propertyQueryElements = new ArrayList<CriteriaElement>();	  	
			propertyQueryElements.add(DetachedCriteria.eq(Address.PROPERTY_EMPLOYEE, entity));
			propertyQueryElements.add(DetachedCriteria.desc(Address.PROPERTY_ID));

			return propertyQueryElements;
		}
		
		return super.getChoicePropertyQueryElements(entity, property);
	}
The list of CriteriaElement(s) returned will be used to build query criteria. Return null if entity selection is not available.

Form Entity Persistence

During creating or merging the enclosing entity in persistence, the EntityFormSupportBean will call prepareFormEntityBeforeCommit(...) twice for each entity form bean property: one in prePersist or preMerge phase, the other in postPersist or postMerge phase.

Based on the O/R mapping between Employee and Address, an Employee's home and mailing addresses are nullable, but an Address' employee is not nullable. During prePersist or preMerge, prepare the address for persistence by setting employee and active properties. Return false to tell the framework to persist or merge the Address entity before persisting or merging the Employee entity. The framework will take care of the circular reference between the Employee and Address entity. During postPersist or postMerge, doing nothing. It does not matter to return true or false since the framework will not persist or merge the Address again if it has already persisted or merged in persistence.


	@Override
	protected <M extends PersistenceEntity> boolean prepareFormEntityBeforeCommit(
			Employee entity, String entityFormProperty, M formEntity, boolean prePersistOrMerge,
			PersistenceEntityManager peManager, QueryDescriptor qd) throws SystemException {
		boolean createdOrMerged = super.prepareFormEntityBeforeCommit(entity, entityFormProperty, formEntity, 
				prePersistOrMerge, peManager, qd);
		
		if (!(entityFormProperty.equals(Employee.PROPERTY_HOME_ADDRESS) ||
				entityFormProperty.equals(Employee.PROPERTY_MAILING_ADDRESS)))
			return createdOrMerged;
		
		if (prePersistOrMerge) { // avoid setting twice
			Address addr = (Address) formEntity;
			if (addr.getEmployee() == null)
				addr.setEmployee(entity);
			
			if (addr.getActive() == null)
				addr.setActive(true);
			
			return false;
		}
		
		return createdOrMerged;
	}

What if an Employee's home and mailing addresses are not nullable, but an Address' employee is nullable. In this case, both Address entities must be created before creating the Employee entity. If the Employee is not persisted, any Address' reference to the employee must be removed during prePersist phase, and restored during postPersist phase. An entity can not reference to any transient entities when the entity is persisted or merged in persistence.

	@Override
	protected <M extends PersistenceEntity> boolean prepareFormEntityBeforeCommit(
			Employee entity, String entityFormProperty, M formEntity, boolean prePersistOrMerge,
			PersistenceEntityManager peManager, QueryDescriptor qd) throws SystemException {
		boolean createdOrMerged = super.prepareFormEntityBeforeCommit(entity, entityFormProperty, formEntity, 
				preCreateOrApplyChage, peManager, qd);
		
		if (!(entityFormProperty.equals(Employee.PROPERTY_HOME_ADDRESS) ||
				entityFormProperty.equals(Employee.PROPERTY_MAILING_ADDRESS)))
			return createdOrMerged;
		
		if (prePersistOrMerge) {
			// break the reference to employee if it is transient(not persisted)
			Address addr = (Address) formEntity;
			Employee employee = addr.getEmployee();
			if (employee != null && !employee.isPersisted())
				addr.setEmployee(null);
				
			if (addr.getActive() == null)
				addr.setActive(true);
			
			return false;  // tell framework to create the address
		}
		else {
			// the employee and address have been created/merged
			Address addr = (Address) formEntity;
			if (addr.getEmployee() == null) {
				addr.setEmployee(entity);
				addr.setChanged(true); // set change flag
				return false; // tell framework to merge the address
			}
		}
		
		return createdOrMerged;
	}
Form DesignContext HelpFrames / No Frames