Fork me on GitHub

Extend OfficeFloor Tutorial

This tutorial looks at an example extension of OfficeFloor to show the ease of adding your own extensions to OfficeFloor.

The example used is the JPA plug-in. The code in this tutorial is the complete code used for plugging JPA into OfficeFloor. It also provides the code for graphically configuring JPA.

This tutorial covers implementing a ManagedObjectSource to provide an EntityManager as an available dependency object. It demonstrates some of the features of OfficeFloor dependencies and why they are not just a class object. However, in many cases dependency objects are simple POJOs (Plain Old Java Objects) and an implementation already exists to configure these - ClassManagedObjectSource.

OfficeFloor Plug-in Architecture - Managing Complexity

The OfficeFloor mission lists the available extensions for OfficeFloor. To extend OfficeFloor requires writing an implementation of one or more of these extension interfaces.

The OfficeFloor extension interfaces follow a recursive design pattern that allows building more complex extensions from simpler extensions. The SectionSource is a good example of the recursive complexity growth pattern of OfficeFloor. The SectionSource extension provides a means to connect together WorkSource and ManagedObjectSource extensions to provide a more complex extension. The SectionSource may itself also include other SectionSource extensions to provide even more recursive complexity growth.

This recursive complexity growth follows how businesses manage complexity. Businesses typically take a top down approach to this problem by recursively splitting responsibility. The business is broken down into functions. The functions are broken down into departments. The departments are broken down into teams. Teams are made up of individual workers. OfficeFloor follows this approach for managing complexity except that it takes a bottom up approach to enable more agility in building systems. This recursive complexity growth pattern also serves as the basis of the architecture the OfficeFloor Team is using to build the open integration Platform-as-a-Service.

The atomic extensions within OfficeFloor are the:

These extensions provide the inversion of control fundamentals that allow the recursive complexity growth architecture of OfficeFloor. See the OfficeFloor mission and the OfficeFloor Architecture for more details.

OfficeFloor Extension Type

Each extension in OfficeFloor has the naming convention, ExtensionSource. All Sources follow a design pattern of the following methods:

  • PropertyList getSpecification()
  • void init(ExtensionSourceContext context) and ExtensionMetaData getMetaData()

On the instantiating the source by its default constructor, the getSpecification() provides the list of properties that must be provided to configure the Source. This method is typically used by OfficeFloor graphical configuration to know what properties to configure to use the Source.

The init(...) configures the Source from the properties specified in the OfficeFloor graphical configuration.

The getMetaData() provides type information regarding the Source. Each ExtensionSource has a corresponding ExtensionType that is configured from the results of the getMetaData() method. Note that the type information may use the property values and be different based on the property values configured. Also for higher level Sources, the init(...) and getMetaData() methods are replaced by a sourceExtension(...) method to allow the recursive complexity growth. The ExtensionType has two key uses:

  • OfficeFloor graphical configuration to know the type information for the Source to graphically configure it with other Sources.
  • Allow higher level extensions to use the type information to dynamically configure them with other extensions to create more complex extensions (recursive complexity growth pattern explained above).

To simplify creating extensions, each ExtensionSource interface has a corresponding AbstractExtensionSource abstract class. For JPA the following is the specification and meta-data methods. Note that for simplicity the init(...) and getMetaData() methods are implemented by the one abstract method loadMetaData(...). The following code also contains the class signature and fields to have the code sequentially displayed through this tutorial.

public class JpaEntityManagerManagedObjectSource extends
		AbstractManagedObjectSource<None, None> implements
		ExtensionInterfaceFactory<EntityTransaction> {

	/**
	 * Name of property providing the persistence unit name.
	 */
	public static final String PROPERTY_PERSISTENCE_UNIT_NAME = "persistence.unit.name";

	/**
	 * Name of {@link Team} to close the {@link EntityManager}.
	 */
	public static final String TEAM_CLOSE = "CLOSE";

	/**
	 * Persistence unit name.
	 */
	private String persistenceUnitName;

	/**
	 * {@link Properties} to configure the {@link EntityManagerFactory}. Enables
	 * providing differing {@link Properties} values between environments.
	 */
	private Properties properties;

	/**
	 * {@link EntityManagerFactory}.
	 */
	private EntityManagerFactory entityManagerFactory;

	/*
	 * ================ ManagedObjectSource ========================
	 */

	@Override
	protected void loadSpecification(SpecificationContext context) {
		context.addProperty(PROPERTY_PERSISTENCE_UNIT_NAME, "Persistence Unit");
	}

	@Override
	protected void loadMetaData(MetaDataContext<None, None> context)
			throws Exception {
		ManagedObjectSourceContext<None> mosContext = context
				.getManagedObjectSourceContext();

		// Obtain the persistence unit name
		this.persistenceUnitName = mosContext
				.getProperty(PROPERTY_PERSISTENCE_UNIT_NAME);

		// Obtain the additional properties for the entity manager factory
		this.properties = mosContext.getProperties();

		// Specify meta-data
		context.setObjectClass(EntityManager.class);

		// Provide close entity manager
		CloseEntityManagerTask closeTask = new CloseEntityManagerTask();
		ManagedObjectWorkBuilder<CloseEntityManagerTask> recycleWork = mosContext
				.getRecycleWork(closeTask);
		ManagedObjectTaskBuilder<CloseEntityManagerDependencies, None> recycleTask = recycleWork
				.addTask("CLOSE", closeTask);
		recycleTask.linkParameter(
				CloseEntityManagerDependencies.MANAGED_OBJECT,
				RecycleManagedObjectParameter.class);
		recycleTask.setTeam(TEAM_CLOSE);

		// Extension interface
		context.addManagedObjectExtensionInterface(EntityTransaction.class,
				this);
	}

As the ManagedObjectSource are responsible for providing dependency objects, there are three aspects of type information provided:

  • The type of the object provided from this Source (i.e. EntityManager)
  • The recycle Task that closes the EntityManager when no longer in scope (typically when the HTTP request has been serviced)
  • An Extension Interface for managing transactions on the EntityManager. This is separate interface that is made available to other extensions, for example the JpaTransactionGovernanceSource to allow managing transactions. Application functionality dependent on the EntityManager can focus on business logic leaving transaction management to the JpaTransactionGovernanceSource. Note that application functionality is itself contained in extensions, WorkSource.

ManagedObjectSource

The remaining methods are specific to the ManagedObjectSource.

	@Override
	public void start(ManagedObjectExecuteContext<None> context)
			throws Exception {
		this.entityManagerFactory = Persistence.createEntityManagerFactory(
				this.persistenceUnitName, this.properties);
	}

	@Override
	protected ManagedObject getManagedObject() throws Throwable {

		// Create the entity manager
		EntityManager entityManager = this.entityManagerFactory
				.createEntityManager();

		// Create and return the managed object
		return new JpaEntityManagerManagedObject(entityManager);
	}

	/*
	 * ======================= ExtensionInterfaceFactory =======================
	 */

	@Override
	public EntityTransaction createExtensionInterface(
			ManagedObject managedObject) {
		JpaEntityManagerManagedObject jpaEntityManagerMo = (JpaEntityManagerManagedObject) managedObject;
		return jpaEntityManagerMo.getEntityTransaction();
	}

}

Managed objects have four aspects to them:

  • ManagedObjectSource that is a instance singleton within the application.
  • The ManagedObjectSource provides ManagedObject instances which provide the management over the dependency object instance.
  • The dependency object instance which is provided from its managing ManagedObject. This is the object used by the application code contained in WorkSource extensions.
  • Extension interfaces available from the ManagedObject that allow extensible management over the dependency object instance. The ExtensionInterfaceFactory is configured in the meta-data and is used to extract the extension interfaces from the ManagedObject instance.

The JPA ManagedObject is as follows.

public class JpaEntityManagerManagedObject implements ManagedObject {

	/**
	 * {@link EntityManager}.
	 */
	private final EntityManager entityManager;

	/**
	 * Initiate.
	 * 
	 * @param entityManager
	 *            {@link EntityManager}.
	 */
	public JpaEntityManagerManagedObject(EntityManager entityManager) {
		this.entityManager = entityManager;
	}

	/**
	 * Obtains the {@link EntityTransaction}.
	 * 
	 * @return {@link EntityTransaction}.
	 */
	EntityTransaction getEntityTransaction() {
		return this.entityManager.getTransaction();
	}

	/**
	 * Closes the {@link EntityManager}.
	 */
	void closeEntityManager() {
		this.entityManager.close();
	}

	/*
	 * ====================== ManagedObject ======================
	 */

	@Override
	public Object getObject() throws Throwable {
		return this.entityManager;
	}

}

WorkSource

The application functionality is plugged into OfficeFloor by WorkSource extensions which provide Task instances containing the application functionality operations. For plugging in POJOs containing application functionality see the ClassWorkSource (and the higher level more useful extension, ClassSectionSource).

As dependency objects are not always passive objects, ManagedObjectSource implementations can register Task instances to undertake particular functionality. A particular Task used by many ManagedObjectSource implementations is registering a recycle Task. The recycle Task is triggered when the ManagedObject goes out of scope.

The JpaEntityManagerManagedObjectSource uses the recycle Task to ensure the EntityManager is closed. The following is the recycle Task code to close the EntityManager. It uses the AbstractSingleTask to simplify the implementation.

public class CloseEntityManagerTask
		extends
		AbstractSingleTask<CloseEntityManagerTask, CloseEntityManagerTask.CloseEntityManagerDependencies, None> {

	/**
	 * Recycle {@link Task} dependencies to close the {@link EntityManager}.
	 */
	public static enum CloseEntityManagerDependencies {
		MANAGED_OBJECT
	}

	/*
	 * ============================ Task ==========================
	 */

	@Override
	@SuppressWarnings("unchecked")
	public Object doTask(
			TaskContext<CloseEntityManagerTask, CloseEntityManagerDependencies, None> context)
			throws Throwable {

		// Obtain the recycle parameter
		RecycleManagedObjectParameter<JpaEntityManagerManagedObject> parameter = (RecycleManagedObjectParameter<JpaEntityManagerManagedObject>) context
				.getObject(CloseEntityManagerDependencies.MANAGED_OBJECT);

		// Obtain the managed object
		JpaEntityManagerManagedObject managedObject = parameter
				.getManagedObject();
		
		// Close the Entity Manager
		managedObject.closeEntityManager();

		// Closed, nothing further
		return null;
	}

}

Unit Test

Each ExtensionSource has a corresponding ExtensionLoaderUtil that eases unit testing implementations. The following unit tests show testing the specification and meta-data.

public class JpaEntityManagerManagedObjectSourceTest extends
		AbstractJpaTestCase {

	/**
	 * Validate the specification.
	 */
	public void testSpecification() {
		ManagedObjectLoaderUtil
				.validateSpecification(
						JpaEntityManagerManagedObjectSource.class,
						JpaEntityManagerManagedObjectSource.PROPERTY_PERSISTENCE_UNIT_NAME,
						"Persistence Unit");
	}

	/**
	 * Validate the type.
	 */
	public void testType() {

		// Create the expected type
		ManagedObjectTypeBuilder type = ManagedObjectLoaderUtil
				.createManagedObjectTypeBuilder();
		type.setObjectClass(EntityManager.class);
		type.addTeam(JpaEntityManagerManagedObjectSource.TEAM_CLOSE);
		type.addExtensionInterface(EntityTransaction.class);

		// Validate type
		ManagedObjectLoaderUtil
				.validateManagedObjectType(
						type,
						JpaEntityManagerManagedObjectSource.class,
						JpaEntityManagerManagedObjectSource.PROPERTY_PERSISTENCE_UNIT_NAME,
						"test");
	}
	

Once the type information is correct, the implementation may be utilised in running instances of OfficeFloor to test it. The following unit tests demonstrate using the JpaEntityManagerManagedObjectSource to read and write data from a database.

Note that the OfficeFloor graphical configuration uses the same API to configure an application as used by the unit tests below. This ensures consistency between use in the unit tests and in applications.

	/**
	 * Ensure able to read entry from database.
	 */
	public void testRead() throws Exception {

		// Add entry to database
		Statement statement = this.connection.createStatement();
		statement
				.execute("INSERT INTO MOCKENTITY (NAME, DESCRIPTION) VALUES ('test', 'mock read entry')");
		statement.close();

		// Configure the application
		AutoWireApplication app = new AutoWireOfficeFloorSource();
		app.addSection("READ", ClassSectionSource.class.getName(),
				ReadWork.class.getName());
		AutoWireObject jpa = app.addManagedObject(
				JpaEntityManagerManagedObjectSource.class.getName(),
				new ManagedObjectSourceWirer() {
					@Override
					public void wire(ManagedObjectSourceWirerContext context) {
						context.mapTeam(
								JpaEntityManagerManagedObjectSource.TEAM_CLOSE,
								new AutoWire(EntityManager.class));
					}
				}, new AutoWire(EntityManager.class));
		jpa.addProperty(
				JpaEntityManagerManagedObjectSource.PROPERTY_PERSISTENCE_UNIT_NAME,
				"test");
		jpa.addProperty("datanucleus.ConnectionDriverName",
				jdbcDriver.class.getName());

		// Invoke task to retrieve entity
		AutoWireOfficeFloor officeFloor = app.openOfficeFloor();
		Result result = new Result();
		officeFloor.invokeTask("READ.WORK", "task", result);

		// Validate the result
		assertNotNull("Should have result", result.entity);
		assertEquals("Incorrect name", "test", result.entity.getName());
		assertEquals("Incorrect description", "mock read entry",
				result.entity.getDescription());
	}

	/**
	 * Holder for the result.
	 */
	public static class Result {
		public MockEntity entity = null;
	}

	/**
	 * Mock {@link Work} for test by similar name.
	 */
	public static class ReadWork {
		public void task(@Parameter Result result, EntityManager entityManager) {

			// Retrieve the entity
			TypedQuery<MockEntity> query = entityManager.createQuery(
					"SELECT M FROM MockEntity M WHERE M.name = 'test'", MockEntity.class);
			MockEntity entity = query.getSingleResult();

			// Specify the result
			result.entity = entity;
		}
	}

	/**
	 * Ensure able to write entry to database.
	 */
	public void testWrite() throws Exception {

		// Configure the application
		AutoWireApplication app = new AutoWireOfficeFloorSource();
		app.addSection("WRITE", ClassSectionSource.class.getName(),
				WriteWork.class.getName());
		AutoWireObject jpa = app.addManagedObject(
				JpaEntityManagerManagedObjectSource.class.getName(),
				new ManagedObjectSourceWirer() {
					@Override
					public void wire(ManagedObjectSourceWirerContext context) {
						context.mapTeam(
								JpaEntityManagerManagedObjectSource.TEAM_CLOSE,
								new AutoWire(EntityManager.class));
					}
				}, new AutoWire(EntityManager.class));
		jpa.addProperty(
				JpaEntityManagerManagedObjectSource.PROPERTY_PERSISTENCE_UNIT_NAME,
				"test");
		jpa.addProperty("datanucleus.ConnectionDriverName",
				jdbcDriver.class.getName());

		// Invoke task to retrieve entity
		AutoWireOfficeFloor officeFloor = app.openOfficeFloor();
		officeFloor.invokeTask("WRITE.WORK", "task", null);

		// Validate the entry written to database
		Statement statement = this.connection.createStatement();
		ResultSet results = statement
				.executeQuery("SELECT NAME, DESCRIPTION FROM MOCKENTITY");
		assertTrue("Should have entry", results.next());
		assertEquals("Incorrect name", "write", results.getString("NAME"));
		assertEquals("Incorrect description", "mock write entity",
				results.getString("DESCRIPTION"));
	}

	/**
	 * Mock {@link Work} for test by similar name.
	 */
	public static class WriteWork {
		public void task(EntityManager entityManager) {

			// Create entry to write to database
			MockEntity entity = new MockEntity("write", "mock write entity");

			// Write entry to database
			entityManager.persist(entity);
		}
	}

}

Eclipse Extension

By default, OfficeFloor extensions are configured by a generic wizard that uses the properties specified by the getSpecification() method. To provide more intuitive configuration of the ExtensionSource, an ExtensionSourceExtension may be used to provide a bespoke Wizard page for configuring the ExtensionSource. This is optional as Source implementations can be configured by the OfficeFloor Eclipse plug-ins without this. However, it is recommended by the OfficeFloor Team to provide these OfficeFloor Eclipse plug-in extensions for any Source implementations published for re-use to ensure using the published Source is easier and more intuitive.

The code for the ExtensionSourceExtension of JpaEntityManagerManagedObjectSource is as follows.

public class JpaEntityManagerManagedObjectSourceExtension
		implements
		ManagedObjectSourceExtension<None, None, JpaEntityManagerManagedObjectSource> {

	/*
	 * ===================== ManagedObjectSourceExtension ======================
	 */

	@Override
	public Class<JpaEntityManagerManagedObjectSource> getManagedObjectSourceClass() {
		return JpaEntityManagerManagedObjectSource.class;
	}

	@Override
	public String getManagedObjectSourceLabel() {
		return "JPA EntityManager";
	}

	@Override
	public void createControl(Composite page,
			ManagedObjectSourceExtensionContext context) {

		// Specify layout of page
		SourceExtensionUtil.loadPropertyLayout(page);

		// Provide property for persistence unit
		SourceExtensionUtil
				.createPropertyText(
						"Persistence Unit",
						JpaEntityManagerManagedObjectSource.PROPERTY_PERSISTENCE_UNIT_NAME,
						null, page, context, null);

		// Provide additional dynamic properties for the Entity Manager
		SourceExtensionUtil
				.createPropertyList(
						"Properties",
						page,
						context,
						JpaEntityManagerManagedObjectSource.PROPERTY_PERSISTENCE_UNIT_NAME);
	}

	@Override
	public String getSuggestedManagedObjectSourceName(PropertyList properties) {
		return "EntityManager";
	}

}

Making this available to the OfficeFloor Eclipse plug-ins is undertaken by wrapping the above in an Eclipse plug-in with the following extension configuration. There are extension points following the below naming convention for all Sources within OfficeFloor.

<extension
      point="net.officefloor.ui.managedobjectsources">
   <managedobjectsource
         class="net.officefloor.eclipse.jpa.JpaEntityManagerManagedObjectSourceExtension">
   </managedobjectsource>
</extension>

Next

Please see the other tutorials for further features.