The Entity Manager

In This Topic

 


 

The Entity Manager is a service that manages entities. An entity is an object like a Book, Person or Car. Entities, or domain objects, are defined and registered by a component within a plugin. Entities are implemented as a Java class with particular entity annotations and always defines an implementation class and an interface. Both the interface and implementation are registered by the Entity Manager. The Entity Manager supports all CRUD actions on the entity. So it can Create, Retrieve, Update and Delete entities.

The Entity Manager is a required guideline (see G125).

Implementing an Entity

As stated above, the implementation of an entity consists of two Java classes: the entity implementation and the interface. The interface contains the public methods that may be invoked by any external class, even Java classes from another plugin. The interface must extend the Identifiable interface, which holds getters and setters for the WmId. The WmId is the unique identifier for the entity inside the Entity Manager’s registry.

The WmId is an interface. In most cases you will use the JcrWmId implementation of this interface for identifying entities stored inside the JCR. An example interface of an entity with two properties, name and text, is displayed below.

 

public interface CustomEntity extends Identifiable {
	String getName();
	void setName(String name);

	String getText();
	void setText(String text);
}

 

The entity implementation implements the entity interface described above. To identify that this Java class contains the definition of an Entity, four specific Entity annotations must be added to the header of the class.

  1. @Entity: First, the @Entity annotation must be added to the class header to identify the class as definition of an entity. The annotation can take one argument: websiteSpecific. To make @Entity website specific, the syntax is:

    @Entity(websiteSpecific=true)
  2. @Interfaces: Secondly, the interface that is associated with this entity must be defined using the @Interfaces annotation. It takes the full classname (including package) of the associated interface as argument.
  3. @Namespace: Thirdly, the namespace for this entity must be defined for this entity using the @Namespace annotation. The annotation takes two arguments, the prefix and the URI.

    The plugin defines one namespace and URI for all classes contained by the plugin, including the entity. Usually the namespace and URI are defined in the Activator of the plugin or a Java class containing all constants, like IDs, namespace and URI.



  4. @Nodetype: Finally, when the Entity will be stored inside the JCR, the @NodeType annotation should be used to define the node type, super type and mixin node types of the node that represents the entity in the JCR. The @NodeType annotation takes four arguments; name, registerNodeType, supertype and mixin.

    • The name argument defines the name of the node type prefixed by the namespace prefix followed by “:”.
    • The registerNodeType argument indicates that the node type must be registered at the Entity Manager; always specify true for this argument.
    • The supertype argument defines the primary node type of the node in the JCR. If there is no particular node type that the entity node type should extend, use the JCR base node type. This base node type is defined in nl.gx.webmanager.services.jcrrepository.Constants.NT_BASE_QUALIFIED_NAME so it is recommended to use this static variable.
    • Finally, the mixin argument defines the additional super node types of the node. In many cases the node associated with the entity must be able to be referenced and thus extend it (defined by Constants. MIX_REFERENCEABLE_QUALIFIED_NAME).

For example, the annotations for an entity implementing the nl.gx.product.domain.CustomEntity interface could look like this:

// Marks this class as being an entity
@Entity

// Defines the interface that identifies this entity 
@Interfaces("nl.gx.product.domain.CustomEntity")

// Defines the namespace of this entity
@Namespace(prefix = Activator.NAMESPACE_PREFIX,	URI = Activator.NAMESPACE_URI
)

// Defines the node type of this entity
@NodeType(name = Activator.NAMESPACE_PREFIX + ":" +  Activator.CUSTOM_ENTITY_NODETYPE, registerNodeType = true, supertype = Constants.NT_BASE_QUALIFIED_NAME, mixin = Constants.MIX_REFERENCEABLE_QUALIFIED_NAME 
)


The implementation of the entity class itself is rather straight forward. To identify properties that must be persisted, the corresponding getter for the property should be prefixed with the @Property annotation. Since the entity implementation implements the Identifiable interface, the entity implementation must implement getId and setId. As stated before, when the entity is stored in the JCR the WmId used by the entity is most likely the JcrWmId implementation of that interface.

For proper Entity implementation it is also a good idea to implement the compareTo and equals methods. An example Entity implementation of an entity called CustomEntityImpl implementing the CustomEntity interface that contains two properties, name and text, is shown below. JavaDoc annotations are omitted in this example to decrease its size.

@Entity

@Interfaces("nl.gx.helloWorld.helloWorldPanel.domain.CustomEntity")

@Namespace(prefix = Activator.NAMESPACE_PREFIX, URI = Activator.NAMESPACE_URI
)

@NodeType(name = Activator.NAMESPACE_PREFIX + ":" + Activator.CUSTOM_ENTITY_NODETYPE, registerNodeType = true, 
supertype = Constants.NT_BASE_QUALIFIED_NAME, mixin = Constants.MIX_REFERENCEABLE_QUALIFIED_NAME)

public class CustomEntityImpl implements CustomEntity,Comparable<CustomEntityImpl>{
	private String myName;
	private String myText;
	private JcrWmId myId;

	@Property
	public String getName() {
	    return myName;
	}
	public void setName(String name) {
		myName = name;
	}
	@Property
	public String getText() {
	    return myText;
	}
	public void setText(String text) {
	    myText = text;
	}
	public WmId getId() {
	    return myId;
	}
	public void setId(WmId id) {
	    myId = (JcrWmId) id;   
	}
	public int compareTo(CustomEntityImpl otherEntity) {
	    int result = getName().compareTo(otherEntity.getName());
		if (result == 0) {
			result = getId().toString().compareTo(otherEntity.getId().toString());
		}
	return result;
	}
	public boolean equals(Object obj) {
	    if (obj instanceof CustomEntityImpl) {
			return ((CustomEntityImpl) obj).getId().toString().equalsIgnoreCase(getId().toString());
		}
		return false;
	}
}

 

Back to Top

 


Registering an Entity

The implementation of the entity does not automatically register the entity with the Entity Manager to be managed by it. To do so, this must explicitly be defined in the component definition that defines the entity contained by the Activator. There are three methods available on the ComponentDefinitionImpl that are relevant to entity registration.

The setEntityClassNames method must be invoked in order to define the classnames of all entity implementations to be registered. Furthermore, the classnames of the entity factory and Persistence Manager must be defined using the setEntityFactoryClassName and setPersistenceManagerClassName methods. The purpose of these classes is to enable the extensibility of the CRUD actions implemented by the Entity Manager. In this version of XperienCentral however, it is recommended that you only use the default implementations, which are DefaultJcrEntityFactoryImpl and DefaultJcrPersistenceManager. An example of the entity related definitions in the component definition for the CustomEntity example is shown below.

componentDefinition.setEntityClassNames(new String[] {CustomEntityImpl.class.getName()});
componentDefinition.setEntityFactoryClassName(DefaultJcrEntityFactoryImpl.class.getName());
componentDefinition.setPersistenceManagerClassName(DefaultJcrPersistenceManager.class.getName());

 

 

Do not invoke setEntityClassNames(…) on the same entity in different component definitions. This causes a non-deterministic definition of the component that owns the entity and will cause errors in some occasions, like when purging the component.

 

Back to Top

 


Using the Entity Manager

The Entity Manager is implemented as a service. This means that the component that uses the Entity Manager must define a required service dependency with the nl.gx.webmanager.services.entitymanager.EntityManager interface, therefore the Component definition inside the Activator must contain the following code snippet:

ComponentDependencyImpl emServiceDependency = new ComponentDependencyImpl();
emServiceDependency.setServiceName(EntityManager.class.getName());
emServiceDependency.setRequired(true);
componentDdefinition.setDependencies(new ComponentDependency[]{emServiceDependency});

 

As a result, the Entity Manager service will be injected into the component implementation class. Furthermore, the bundle dependency must be defined in the pom.xml of the plugin. The following code snippet illustrates how this dependency is defined within the <dependencies> section of the pom.xml. Note that the <version> attribute should state the XperienCentral version against which the plugin is built.

 

<dependency>
	<groupId>nl.gx.webmanager.bundles</groupId>
	<artifactId>XperienCentral-entitymanager-bundle</artifactId>
	<scope>provided</scope>
	<version>${parent.version}</version>
<dependency>
<dependency>
	<groupId>nl.gx.webmanager.bundles</groupId>
	<artifactId>XperienCentral-entitydomain-bundle</artifactId>
	<scope>provided</scope>
	<version>${parent.version}</version>
</dependency>

 

Back to Top

 


Entity Management in the Controller

Now that the Entity is defined and registered with the Entity Manager, the next step is to implement the create, update and delete actions on the entity. This is typically done in the onSubmit of the component’s controller. The onSubmit should handle three different types of actions to be performed on the entities:

  1. Creating entities using EntityManager.getInstance and EntityManager.persist
  2. Saving modified entities using EntityManager.save
  3. Deleting entities using EntityManager.delete

The difference between EntityManager.persist and EntityManager.save is that persist() should be invoked when the entity is saved for the first time, and thus does not yet exist in the internal storage system, and save() should be invoked on entities that have previously been persisted.

Usually, properties are transferred from the form backing object into the Entity before it is created or updated. The easiest way to copy the properties is to use the BeanUtils utility class provided by the Spring framework. The only prerequisite is that the property names of form backing object match the corresponding properties of the Entity. To copy properties from the form backing object called CustomTabFBO to the entity, simply use:

 

org.springframework.beans.BeanUtils.copyProperties(myCustomTabFBO, entity);

 

An example implementation of the onSubmit in the component controller:

 

public void onSubmit(HttpServletRequest request, HttpServletResponse response, Object
command, BindException errors, ModelAndView modelAndView) throws Exception {
	super.onSubmit(request, response, command, errors, modelAndView);

	// Save the entity that is currently being edited
	if (myCustomTabFBO.getEntity() != null) {
		CustomEntity entity = myCustomTabFBO.getEntity();
		BeanUtils.copyProperties(myCustomTabFBO, entity);
		myEntityManager.save(entity);
	}

	// Check if a new entity must be created
	if (myCustomTabFBO.getPanelAction().equals("createEntity")) {
		// Create the entity
		CustomEntity newEntity = myEntityManager.getInstance(CustomEntity.class);

		// Persist the entity for the first time
		myEntityManager.persist(newEntity);

		// Select the new entity in the panel
		myCustomTabFBO.setSelectedEntity(newEntity);
	}

	// Check if existing entities must be deleted
	if (myCustomTabFBO.getRemoveEntities() != null) {
		for (CustomEntity entity : myCustomTabFBO.getRemoveEntities()) {
			// Delete the entity
			myEntityManager.delete(entity);
		}
	}

 

Back to Top

 


Entity Retrieval

Before existing entities can be updated, they must first be retrieved. For this, the Entity Manager supports two methods; find(WmId) for retrieving entities for which the ID is known and getAll(Class<T>) to retrieve all entities implementing a particular Entity interface.

Usually the getAll method is used in the referenceData method of the controller in order to retrieve all entities and insert them into the reference data map. The getAll method returns an unordered set containing the entities in a random order. The order can change between different HTTP requests. For this reason the set should be ordered when it is displayed in a table or list by the JSP. The example below shows how to retrieve the entities from the Entity Manager, sort them and put them onto a property called allEntities.

 

public Map<String, Object> referenceData(HttpServletRequest request, Object command, Errors errors) throws Exception {
	Map<String, Object> referenceData; referenceData = super.referenceData(request, command, errors);

	// Retrieve all custom entities and sort them
	Set<CustomEntityImpl> entities =
		myEntityManager.getAll(CustomEntityImpl.class);
	List<CustomEntityImpl> entityList = 
		new ArrayList<CustomEntityImpl>();
	for (CustomEntityImpl entity: entities) {
		entityList.add(entity);
	}
	Collections.sort(entityList);

	// Write the sorted list of all entities to the reference data
	referenceData.put("allEntities", entityList);
	
	return referenceData;
}

 

The above example retrieves all entities, but sometimes you might want to retrieve just one particular entity for which the ID is known. You would do this, for example, to edit a particular entity. To retrieve the entity from the Entity Manager by its WmId, simply invoke:

 

myEntityManager.find(jcrWmId);

 

The XperienCentral API however already supports a basic property editor for all Entities stored in the JCR. This property editor is called EntityPropertyEditor and supports the conversion of entities stored in the JCR to a String that identifies that entity and vice versa. To register this Property Editor, simply create an instance of this Property Editor for the entity in the initBinder method of the controller:

 

public void
initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
	super.initBinder(request, binder);

	// Register a custom editor for the custom entity
	binder.registerCustomEditor(CustomEntity.class,	new EntityPropertyEditor<CustomEntity>(myEntityManager));
}

 

Back to Top

 


Property Annotations

The @Property annotation before a method indicates that the method is the getter method for a particular property that should be persisted by the Entity Manager. The property may be of a primitive type like String, boolean and Integer. Complex object types are supported including FileResource. Complex property types are discussed in detail in this topic.

@Property

When the regular @Property annotation is used for a complex object type, the property is created as a reference. This means that the physical location of the object in the persistence system (for example the JCR) is not defined; it may be stored anywhere in that persistence system. The property itself only stores a reference to this object (in the JCR, for example, it stores the UUID of the node as String property).

The @Property annotation has the following properties:

 

PropertyDescription
name

The name of this property in the repository. Leave this empty if you want to use the same name as the property in the Java class.

type

The JSR-170 property type to use. The default is PropertyType.UNDEFINED which means that the type will be determined by the type of the property in the Java class.

mandatory

Specifies whether this property is mandatory. The default is false.

multiple

Specifies whether the property may have more than one value. The default is false.

constraints

Add JSR-170 constraints to the property. By default no constraints are added.

@Child

The @Child annotation can be used to indicate that the object should not be stored as a reference property but as a child property of this entity. As a result, the physical location of the object will be such that there will be a parent-child relation between the entity and the object to which is referred by the @Child annotation.

In the case of the JCR this means that the node that corresponds to the entity is a parent node of the node that corresponds to the object of the property marked with the @Child annotation. This is an important difference compared to the “by reference” approach, for example when using XPATH queries to search for the child node.

The @Child annotation has the following properties:

 

PropertyDescription
name

The name of this property in the repository. Leave this empty if you want to use the same name as the property in the Java class.

mandatory

Specifies whether this property is mandatory. The default is false.

allowsSameNameSiblings

Specifies whether this child node can have same-name siblings. In other words, whether the parent node can have more than one child node with the same name. The default is false.

requiredPrimaryTypes

Specifies the minimum set of primary node types that the child node must have. The default is {http://www.jcp.org/jcr/nt/1.0}base which is the JSR-170 base type of all node types.

defaultPrimaryType

Specifies the default primary node type that will be assigned to the child node if it is created without an explicitly specified primary node type. The default is {http://www.jcp.org/jcr/nt/1.0}unstructured which indicates the unstructured node type.

 

 

  • The @Child annotation supports multiple values by returning an array of the child class (For example returning CustomEntity[]).
  • The @Child entity is an alternative to the @Property annotation, they should not be used both.

@Collection

The @Collection annotation can be used for properties of which the object type implements java.util.Collection. This annotation offers more advanced options mainly by supporting so called "lazy collections". A lazy collection is a collection of objects of which the objects themselves are not instantiated upon instantiation of the collection itself. There is therefore a significant difference between a regular collection (that is,  a Set) of CustomEntity objects and a lazy collection of CustomEntity objects. While the regular collection will contain a list of references to the actual instances of CustomEntity objects, the lazy collection only contains a list of references without the CustomEntity objects themselves being loaded upon creation of the collection itself. This provides an important performance gain when such a collection contains many objects and nothing is actually done with the objects in the collection themselves. A lazy collection instantiates the objects within the collection only at the time they are needed.

The @collection annotation has the following properties:

 

PropertyDescription
type

The type of the collection property, which must be either Type.LIST or Type.Set.

memberType

Specifies the class associated with the objects contained by the collection.

lazy

Specifies whether the collection should use lazy loading or not. The default is true.

reference

Specifies if the objects contained by the reference should be stored by reference or as child objects. The default is by reference.

 

 

The @Collection annotation should be used in combination with the @Property annotation. If only the @Collection annotation is provided, the property will not be identified as a property of the entity.

 

Back to Top

 


Components of the Entity Manager

The Entity Manager comprises several components that together form a layered architecture, as depicted in the image below. Each layer delegates method calls to the appropriate component on the layer below it.

 

 

 

Entity Manager

The Entity Manager is available as a service in the XperienCentral platform. Its function is to hide all underlying layers from the plugin programmer. Internally, the Entity Manager manages a cache for each active session.

Entity Domains

In the above image, two types of Entity Domains are visible directly beneath the Entity Manager. Such Entity Domains represent a namespace for identifying entities. They each provide an implementation of Identifiable specifically for this EntityDomain. The JcrEntityDomain and its accompanying implementation of WmId and JcrWmId are available. It is the responsibility of the EntityManager to select the right EntityDomain for an Entity and then delegate to it.

Entity Factories

Entity Factories comprise the next layer. They provide a mechanism for post-processing entities after they have been constructed. Usually only one type of Entity Factory is used, namely the DefaultJcrEntityFactoryImpl. Each Entity Factory can handle multiple types of entities. Basically there is one EntityFactory per Component that handles all entities specified by that Component. It is the responsibility of Entity Domain to delegate to the appropriate Entity Factory for a given type of entity.

Persistence Managers

The last layer within the Entity Manager is formed by the Persistence Managers. It is the responsibility of the Persistence Managers to handle the communication with the underlying persistent storage and construct entities. One Persistence Manager is instantiated per entity type which handles all requests for that type of entity. It is the responsibility of the Entity Factories to manage those Persistence Managers and delegate to them. Typically only one type of Persistence Manager is used, namely the DefaultJcrPersistenceManager.

 

Back to Top

 


The Entity Manager Versus JCRUtil

When you generate an element or media item from its archetype you will notice that these components do not use the Entity Manager to persist their properties. The element and media item implementations are marked with the @NodeType and @Property annotations but note the missing @Entity annotation. The missing @Entity annotation indicates that this class is not managed by the Entity Manager.

Persistent classes not managed by the Entity Manager are entities for which its lifecycle is managed by XperienCentral. For example, if a custom element is added to a page in XperienCentral, it is the XperienCentral framework that creates a new instance of that element. If the custom element subsequently is marked for deletion and the page is submitted, the element will be deleted by the XperienCentral framework.

This is different from entities managed by the Entity Manager. With those entities you will have full control over the lifetime of the entity instances. To create such an entity instance, you must explicitly invoke the Entity Manager (that is, an entity in a panel), the same applies for deleting the entity.

Persistent classes not managed by the Entity Manager are:

 

Persistent ClassDescription
Custom media item implementation

Class that extends MediaItemArticleVersionImpl or MediaItemVersionImpl, that is, CustomMediaItemArticleVersionImpl.

Custom element implementation

Class that extends ElementBase, that is, CustomElementImpl.

A persistent class that is not managed by the Entity Manager may persist its properties using the nl.gx.webmanager.foundation.JcrUtil class. This class contains methods to read and write values to and from JCR nodes. The getters and setters of an entity managed by the Entity Manager usually only store the value in a class variable and persist the entity by using EntityManager.persist(). Using JCRUtil is an alternative to EntityManager.persist() however it should be invoked to persist each property individually. For example, to persist the company property:

 

public void setCompany(String company) {
	JcrUtil.setString(getPrivateNode(), WCBConstants.NAMESPACE_PREFIX + ":company", company);
}

 

 

  • The getPrivateNode() receives the node instance that is created automatically by the XperienCentral framework and that contains the persistent properties.
  • Persistent classes not managed by the Entity Manager can still persist their properties using the Entity Manager. To do so add only one child property to the class that has the same type as the entity managed by the Entity Manager.

 

Back to Top