Building a business component for the WebSphere Portal composite application infrastructure

This article provides an overview of how to write a business component for the composite application infrastructure in IBM® WebSphere® Portal. It includes a sample implementation of a business component that demonstrates how the composite application infrastructure can be utilized, plus best practices to leverage and pitfalls to avoid. This content is part of the IBM WebSphere Developer Technical Journal.

Hendrik Haddorp (hendrik.haddorp@de.ibm.com), Developer, IBM Boeblingen Development Lab, IBM

Hendrik Haddorp is the Technical Lead of the Composite Application Infrastructure in WebSphere Portal, working at the IBM Research and Development Laboratory in Boeblingen, Germany. He joined the WebSphere Portal Development team in 2003.



Izidor Jager (JAGER@de.ibm.com), Software Developer, IBM

Izidor Jager is a Software Developer in the IBM Development Laboratory in Boeblingen. He joined the WebSphere Portal development team in 2005. His main area is the Composite Application Infrastructure. Izidor received a Diploma of Computer Science from the University of Stuttgart.



Sven Stueven (STUEVEN1@de.ibm.com), Software Developer, IBM

Sven Stueven is a Software Developer in the IBM Development Laboratory in Boeblingen. He joined the WebSphere Portal development team in 2009 after working three years in Lab based Services. Sven received a Diploma of Business Informatics from the University of Applied Sciences of Karlsruhe.



10 November 2010

Also available in Chinese Portuguese

Introduction

The IBM WebSphere Portal composite application infrastructure (CAI) enables the creation and management of composite applications. In WebSphere Portal, a composite application is made up of WebSphere Portal artifacts, such as pages, portlets, and a community. A community defines roles that provide access rights, and members who belong to one or more roles.

Additionally, a composite application can contain custom business components. Business components extend the infrastructure and provide management of data in back end systems. Most of the time, a portlet leverages a business component to work with data from a specific back end. For example, a portlet might use a business component to manage documents in a document library.

The sections that follow explain more about CAI, what SPIs (service provider interfaces) and APIs are provided, how these SPIs can be implemented, and how the APIs can be utilized. How a business component is deployed into WebSphere Portal is also explained.

An example of a business component implementation is included with this article. The sample business component is a document library. Hereafter, the term document library will refer to this sample business component.

The source code and Javadoc of the sample document library is available to download, along with a user's guide that explains the sample in detail. In addition, it shows the functionality of the document library and how it utilizes the CAI features.


CAI interfaces

To enable CAI to interact with a business component, the business component code needs to include a business component handler class that implements the CAI SPIs. This enables CAI to call the business components when certain events occur; for example, if an application is serialized into a template or a user is added to an application.

These are the interfaces that a business component handler can implement:

  • Lifecycle
  • Serializable
  • MembershipListener
  • Sensor
  • DisplayInfo
  • ApplicationListener
  • Variability
  • StreamBackupServiceClient

You can find them all (except the last item) in the package com.ibm.portal.app.component2. The last one which is located in com.ibm.portal.app.service.backup. The Javadoc for these interfaces is available within a WebSphere Portal installation in the /PortalServer/doc/Javadoc/api_docs/ directory.

Except for Lifecycle, all other interfaces are optional. The set of interfaces the business component handler needs to implement depends on the features with which the business component wants to integrate. For example, if a business component wants to integrate with the backup capability of CAI, it must implement the Serializable interface.

Whenever a business component cannot perform a callback successfully, it is a best practice to throw a com.ibm.portal.app.exceptions.ComponentException. This enables CAI to start a suitable action; for example, delete a business component that was not initialized successfully.

All callback methods have in common the first parameter as the identifier of a particular business component instance. Thus, a business component handler knows which concrete business component instance must be processed. The identifier is always an instance from the WebSphere Portal class com.ibm.portal.ObjectID and is created by CAI.

Aside from SPIs, CAI also offers APIs. Some worth mentioning include BusinessComponentService and the ApplicationService. BusinessComponentService, for example, can be used to store data in CAI about a business component that is not reflected in the back end. And ApplicationService, for example, can be used to get information about other business components of an application.

Shortly, you will see how the sample business component utilizes the CAI API.


Deploying a CAI business component

CAI utilizes the Eclipse extension point framework that is integrated in IBM WebSphere Application Server to retrieve the business component handler of a business component. Therefore, CAI defines an extension point with the plugin ID com_ibm_portal_app.BusinessComponents. A business component must be an extension of this extension point.

To register a business component at the extension point, a plugin.xml file must be provided. The document library's plugin.xml file is shown in Listing 1.

Listing 1. Plugin.xml for the document library
<plugin id="ai.docLib.sample" version="1.0.0">
 <extension point="com_ibm_portal_app.BusinessComponents" id="DocLibHandler">
      <provider class="com.ibm.wps.ai.sample.doclib.handler.DocLibBCHandlerImpl"/>
 </extension>

The extension with the ID ai.docLib.sample.DocLibHandler defines which Java class in the business component is the business component handler class. The fully qualified class name is the value of the attribute class in the element provider. The document library business component handler class is com.ibm.wps.ai.sample.doclib.handler.DocLibBCHandlerImpl. As mentioned earlier, CAI uses this handler to interact with the business component.

If a business component is registered in the extension framework, the handler can be accessed by a JNDI lookup. The JNDI binding name of a CAI business component maps to this pattern:

portal:extreg/<plugin id><extension id>

For the document library it is: portal:extreg/ai.docLib.sample.DocLibSampleHandler.

If a portlet wants to use a business component, it can inform CAI about this by providing a preference with the name com.ibm.portal.bc.ref. The reference is given in the form of a JNDI name. This enables CAI to create a business component instance as soon as the portlet is added to an application.

The portlet.xml for the document library contains the preference shown in Listing 2.

Listing 2. Portlet preference that defines the business component
<portlet-preferences>
      <preference>
	    <name>com.ibm.portal.bc.ref</name>
		<value>portal:extreg/ai.docLib.sample.DocLibSampleHandler </value>
		<read-only>true</read-only>
	  </preference>
    <portlet-preferences>

The recommended way to bundle the portlet and the business component is to package them in one portlet application archive and deploy the portlet application in WebSphere Portal. The business component is then automatically registered in the extension registry and accessible to CAI.


Lifecycle of a business component

CAI contains a business component container to manage the lifecycle of business components. Consider these five phases in the lifecycle of a business component:

  1. created
  2. activated
  3. pre-deleted
  4. deleted
  5. post-deleted

When a business component is instantiated, it is first created and afterwards activated. These phases are reflected in the Lifecycle interface methods create and activate:

  • Create is called at the beginning of the instantiation phase and triggers the creation of a new business component instance and the initialization of needed resources.
  • Activate is at the end of the instantiation phase in order to activate the business component for usage.

The reason for the two phases is to unify the different ways you can create a business component. A business component can be created at the run time of an application, or during the instantiation of an application out of an application template or a backup. The instantiation follows the pattern: create an empty business component, fill it with data, and then activate it.

Listing 3 shows the document library's Lifecycle create method. It just creates the root folder of the library.

Listing 3. Create business component
publicvoid create(ObjectID bcOID, Map<String, Object> initParameters) 
		throws ComponentException {
	final String method = "create";
	final boolean isTraceOn = LOGGER.isLoggable(Level.FINEST);
	if (isTraceOn) {
		LOGGER.entering(LOG_CLASS, method, bcOID);
	}
	try {
 	//use the system time to generate a unique id for the folder
		createRootFolder(bcOID, new Folder(Long.toHexString(System.nanoTime()), 
			"doclib", "Root Folder"));
	} catch (PersisterException e) {
		throw new ComponentException(e);
	} catch (DocLibException e) {
		throw new ComponentException(e);
	}
	if (isTraceOn) {
		LOGGER.exiting(LOG_CLASS, method);
	}
}

What must be retained by the business component handler is the identifier of a concrete business component identifier. The identifier is created by CAI and passed as the first parameter in the create method. In all subsequent CAI callbacks, this identifier will be passed in to identify which concrete business component instance is affected. The business component code must be able to map this identifier to its back end data. The document library stores the serialized business component identifier in its back end; a column in the corresponding table is reserved for this. Instead of storing the identifier in the back end, it can also be deposited in CAI. The BusinessComponentService can be utilized to store data for a business component.

The activate method of the document library (Listing 4) registers its roles in the portal access control (PAC) component of WebSphere Portal. As you can see, the ApplicationCatalogService is used to get the identifier of the composite application to which the business component belongs. The ApplicationService is utilized to determine the community identifier which is required when a role is registered in PAC.

Listing 4. Activate business component
publicvoid activate(ObjectID bcOID) throws ComponentException {
	final String method = "activate";
	final boolean isTraceOn = LOGGER.isLoggable(Level.FINEST);
	if (isTraceOn) {
		LOGGER.entering(LOG_CLASS, method);
	}
	try {
		ObjectID appOID = getAcsHome().getApplicationCatalogService()
			.findApplicationByContainedBC(bcOID);
		ObjectID cmtyOID = getAsHome().getApplicationService(appOID)
			.getCommunityID();
		createDomainObjectWithRoles(cmtyOID, bcOID);
	} catch (DocLibException e) {
		throw new ComponentException(e);
	} catch (ObjectNotFoundException e) {
		throw new ComponentException(e);
	} catch (ApplicationDataException e) {
		throw new ComponentException(e);
	} catch (NamingException e) {
		throw new ComponentException(e);
	} catch (UnknownApplicationException e) {
		throw new ComponentException(e);
	} catch (AccessControlException e) {
		throw new ComponentException(e);
	}
	if (isTraceOn) {
		LOGGER.exiting(LOG_CLASS, method);
	}
}

It is good practice to define and register domain roles in the activation phase of a business component.

If a business component is destroyed, three phases exists: First the business component is pre-deleted, then deleted and finally post-deleted. These phases are reflected in the Lifecycle interface methods preDelete, delete, and postDelete. To understand the meaning of these phases, let’s look at what it means to delete a composite application.

When a composite application is deleted, it is not entirely deleted but just flagged as deleted, and access to the application is prohibited. This is because the deletion of a composite application might be time-consuming. CAI enables you to defer the time consuming parts of the deletion to a date when the system load is not on a high level. This is accomplished by a special CAI task, the application purger, which runs at a customizable point in time, and ultimately does delete applications marked as deleted.

To support the separation of the more and less time consuming parts of the deletion process, these different deletion phases in the lifecycle of a business component were created.

The preDelete method is invoked when an application is marked as deleted by CAI. The business component should execute actions that cannot be delayed and must be performed immediately. For example, the business component might revoke access to the back end data.

Actions that can be delayed -- ideally the time-consuming ones -- are moved in the phases when the application is finally deleted by the CAI application purger. Then the methods delete and postDelete are called.

In the implementation of the document library, the preDelete method is empty because there is nothing to be done immediately (Listing 5).

Listing 5. Predeletion of business component
public void preDelete(ObjectID bcOID) throws ComponentException {
		// nothing to do	}

The deletion of the document library is deferred to the delete method (Listing 6).

Listing 6. Delete business component
publicvoid delete(ObjectID bcOID) throws ComponentException {
	final String method = "delete";
	final boolean isTraceOn = LOGGER.isLoggable(Level.FINEST);
	if (isTraceOn) {
		LOGGER.entering(LOG_CLASS, method);
	}
	try {
		persister.deleteLibrary(Util.getIdentification().serialize(bcOID));
	} catch (PersisterException e) {
		throw new ComponentException(e);
	} catch (SerializationException e) {
		throw new ComponentException(e);
	} catch (DocLibException e) {
		throw new ComponentException(e);
	}
	if (isTraceOn) {
		LOGGER.exiting(LOG_CLASS, method);
	}
}

It's not necessary to deregister the domain roles in PAC when a business component is destroyed. This is handled by CAI automatically.

The document Library is deleted from the database.

Because the deletion of the document library is the one and only action to be performed when the document library business component is destroyed, there is nothing left for the postDelete method, and so it is empty (Listing 7).

Listing 7. Post delete business component
public void postDelete(ObjectID bcOID) throws ComponentException {
	// nothing to do
}

Utilizing CAI features

CAI provides capabilities to backup and restore applications, and to manage composite application communities and support policies. To utilize these features, a business component must implement corresponding CAI SPIs.

The next sections explain these features in general, and how the document library implements the SPIs in particular. The business component handler for the Document Library implements the CAI SPIs as shown in Listing 8.

Listing 8. CAI SPIs implemented by the document library
public class DocLibBCHandlerImpl implements DocLibBCHandler, Lifecycle, Serializable, 
	Sensor, StreamBackupServiceClient, MembershipListener{
...

Serialization into a backup

A composite application can be serialized in two ways: into a composite application template (a blueprint of a composite application) and into a backup. A backup contains roles, community members, application data, and, usually, the back end data of the business components.

To take part in the serialization process, a business component handler must implement the Serializable Interface. A business component can support different types of serialization. It declares what types it supports via the supportsSerialization method. The document library supports both, serialization to a template and to a backup (Listing 9).

Listing 9. Document library supports backup and template serialization
publicboolean supportsSerialization(SerializationType type) {
		return ((SerializationType.TEMPLATE.equals(type)) || 
			(SerializationType.BACKUP.equals(type)));			
}

Serialization into a template works almost the same, although the stored data is different. While a backup stores exactly the data that is available in an instance, the template is designed for data that every new application instance created from the template should contain.

When a backup is performed, CAI checks which business components that are part of the application support backups at all. To accomplish this, CAI first validates if a business component implements the Serializable interface. After that, it verifies that the component supports backup via the supportSerialization method (Listing 9). If this is the case, the serializeInstance method of the business component handler is called. As this method is called by CAI for all serialization types, the serialization type is passed into the method.

CAI expects the business component to return an input source that is derived from the javax.xml.transform.Source interface. It can be either a DOMSource, SAXSource, or a StreamSource.

From a performance perspective, you should provide a SAXSource when there is a huge amount of back end data, as the other implementations require the data to be kept in memory for some time.

The document library returns a SAXSource (Listing 10) in case of a backup because a document library can hold lots of data. For serialization into a template, it returns a DOMSource.

Listing 10. Serialization of document library returns source instance
public Source serializeInstance(ObjectID bcOID, SerializationType type, 
Map<String, Object> parameters) throws ComponentException {
	...
	try {
		if (SerializationType.BACKUP.equals(type)) {

			Model model = getModel(bcOID);
                  ...
			result = new SAXSource(new BackupSerializationXMLReader(), 
new SerializationInputSource(model, fileToHandleMap));
		}
            else if (SerializationType.TEMPLATE.equals(type)) {
			result = new DOMSource();
		}	
	...
	return result;
	}

The BackupSerializationXMLReader is passed into the constructor of the SAXSource as an XMLReader. This reader creates SAX events by transforming the document library model into an XML representation. This is done in the sendFolder method (Listing 11) in the BackupSerializationXMLReader. CAI provides a ContentHandler to receive the SAX events. The events are then serialized into XML and stored into a file named appliction-instance.xml, which is part of the backup.

Listing 11: Create SAX events for document library entries
private void sendFolder(Folder folder, Model model, ContentHandler contentHandler, 
Map<String, String> fileToHandleMap) throws SAXException {
	final String method = "sendFolder";
	final boolean isTraceOn = LOGGER.isLoggable(Level.FINEST);
	if (isTraceOn) {
		LOGGER.entering(LOG_CLASS, method);
	}
	AttributesImpl atts = new AttributesImpl();
	atts.addAttribute("", ID_ATTRIBUT, ID_ATTRIBUT, "CDATA", folder.getID());
	atts.addAttribute("", NAME_ATTRIBUT, NAME_ATTRIBUT, "CDATA", folder.getName());
	atts.addAttribute("", DESC_ATTRIBUT, DESC_ATTRIBUT, "CDATA", folder.getDesc());
	contentHandler.startElement("", FOLDER_TAG, FOLDER_TAG, atts);
	for (Folder childFolder : model.getChildFolders(folder)) {
		sendFolder(childFolder, model, contentHandler, fileToHandleMap);
	}
	for (File file : model.getFiles(folder)) {
		atts = new AttributesImpl();
		atts.addAttribute("", ID_ATTRIBUT, ID_ATTRIBUT, "CDATA", file.getID());
		atts.addAttribute("", NAME_ATTRIBUT, NAME_ATTRIBUT, "CDATA", 
			file.getName());
		atts.addAttribute("", DESC_ATTRIBUT, DESC_ATTRIBUT, "CDATA", 
			file.getDesc());
		atts.addAttribute("", MIME_TYPE_ATTRIBUT, MIME_TYPE_ATTRIBUT, "CDATA", 
			file.getMimeType());
		atts.addAttribute("", FILE_HANDLE_ATTRIBUT, FILE_HANDLE_ATTRIBUT, 
			"CDATA", fileToHandleMap.get(file.getID()));
		contentHandler.startElement("", FILE_TAG, FILE_TAG, atts);
		contentHandler.endElement("", FILE_TAG, FILE_TAG);
	}
	contentHandler.endElement("", FOLDER_TAG, FOLDER_TAG);
	if (isTraceOn) {
		LOGGER.exiting(LOG_CLASS, method);
	}
}

It is possible to serialize all the back end data this way. Consequently, the entire back end data is written into application-instance.xml. This could, however, result in a very large file. In contrast to the template serialization, CAI provides a mechanism for the backup serialization to write data into additional files besides the application-instance.xml file.

Write large amounts of data into separate files.

The Document Library uses this mechanism to store each document's content into a separate file. This is achieved by implementing the StreamBackupServiceClient interface. During the serialization process, the business component handler registers each file at the StreamBackupService by passing in the file identifier. The service returns a file handle, as you can see in Listing 12.

Listing 12. Register each file at the StreamBackupService
public Source serializeInstance(ObjectID bcOID, SerializationType type, Map<String, 
Object> parameters) throws ComponentException {
…	
   try {
	if (SerializationType.BACKUP.equals(type)) {

		Model model = getModel(bcOID);
		Map<String, String> fileToHandleMap = new HashMap<String, 
			String>();

		// prepare for serialization of files:
		StreamBackupService streamBackupService = (StreamBackupService) 
((List) Util.getContext().lookup(StreamBackupService.JNDI_NAME)).get(0);
		Set<String> fileIDs = model.getAllFilesID();
		for (String fileID : fileIDs) {
			String handle = streamBackupService.registerBackupData
(parameters.get(BACKUP_CONTEXT), fileID);
			fileToHandleMap.put(fileID, handle);
		}
		// serialize other stuff into application-instance.xml
		result = new SAXSource(new BackupSerializationXMLReader(), 
new SerializationInputSource(model, fileToHandleMap));

The file handle is mapped to the file identifier and exposed in the application-instance.xml. This data is required during restore to map the file contents to a document in the Document Library model.

To serialize the file content, CAI invokes the backupData method of the business component handler (Listing 13).

Listing 13. Serialize document content into separate file
public void backupData(OutputStream out, String componentID, String handback) 
throws ComponentException {
		final String method = "backupData";
		final boolean isTraceOn = LOGGER.isLoggable(Level.FINEST);
		if (isTraceOn) {
			LOGGER.entering(LOG_CLASS, method, String.format("componentID: 
%s  | handback(=fileID): %s", componentID, handback));
		}
		try {
			ObjectID bcOID = Util.getIdentification().deserialize
(componentID);
			String fileID = handback;
			InputStream in = getFileContent(bcOID, fileID);
			try {
				Util.pipe(in, out);
			} finally {
				in.close();
			}
		} catch (PersisterException e) {
			throw new ComponentException(e);
		} catch (SerializationException e) {
			throw new ComponentException(e);
		} catch (DocLibException e) {
			throw new ComponentException(e);
		} catch (IOException e) {
			throw new ComponentException(e);
		}

		if (isTraceOn) {
			LOGGER.exiting(LOG_CLASS, method);
		}
	}

The handback parameter is the identifier of the file that was passed to the StreamBackupService within the serializeInstance method.

When the backup action is finished, the backup contains a separate file for each document of the document library.

Restore from a backup

A composite application can be deserialized in two ways: from a composite application template or from a backup. The basic control flow is the same for both types. During the deserialization process, all business components that belong to the composite application are created and filled with data.

When a composite application that already exists is restored, it is deleted first and then created again. Consequently, all business components included need to be recreated.

When a restore is performed, CAI checks which business components in the application support the restore feature. This works the same way as when a backup is performed: CAI first checks if a business component implements the Serializable interface. After that, it verifies that the component supports backup via the supportSerialization method (Listing 9).

Filling a business component with data is done in three phases:

  1. CAI calls the initDeserializeInstance method of the business component handler to get a target for the data of the business component.
  2. It then sends the XML data to the provided Result object.
  3. Finally, the finishDeserializeInstance method is invoked.

This final step is actually only required when the component does not use a SAXResult, as the component can otherwise detect the end of the data stream via the endDocument method. In case of an exception during the process, the component's cancelDeserializeInstance method is invoked so that the component can remove anything that was created so far.

Again, CAI calls these business component handler methods in this sequence:

  1. create
  2. initDeserializeInstance
  3. finishDeserializeInstance
  4. activate

The create and activate methods were explained earlier. The initDeserializeInstance method returns a target for the data of the business component. CAI expects the business component to return a target which is derived from the javax.xml.transform.Result interface. It can be either a DOMResult, SAXResult or a StreamResult.

From a performance perspective, you should provide a SAXResult when the back end data is substantial, as the other implementations require that the entire data be read into memory first.

The document library returns a SAXResult in case of a restore (Listing 14). For the deserialization from a template, it returns a DOMResult for demonstration purposes.

Listing 14. Return target for the serialized data
public Result initDeserializeInstance(ObjectID bcOID, SerializationType type, 
Map<String, Object> parameters) throws ComponentException {
	final String method = "initDeserializeInstance";
	final boolean isTraceOn = LOGGER.isLoggable(Level.FINEST);
	if (isTraceOn) {
		LOGGER.entering(LOG_CLASS, method, parameters);
	}
	Result result = null;
	if (SerializationType.BACKUP.equals(type)) {
	// delete root folder
		delete(bcOID);
			// within create there was already a root folder created. 
			// But the ID was generic and do not fit to the restore 
			// root folder id
		result = new SAXResult(new RestoreHandler(bcOID, parameters));
	} else {
		result = new DOMResult();
	}
	if (isTraceOn) {
		LOGGER.exiting(LOG_CLASS, method, result);
	}
	return result;
}

The returned SAXResult is initialized with an implementation of the org.xml.sax.ContentHandler named RestoreHandler that is invoked by CAI during parsing of the serialized representation of the business component.

For example, when the beginning of an XML element is detected during parsing, CAI calls the startElement in the RestoreHandler (Listing 15).

Listing 15. Restore document library entries when SAX event occurs
public void startElement(String namespaceURI, String localName, String qName, 
Attributes atts) throws SAXException {
	final String method = "startElement";
	final boolean isTraceOn = LOGGER.isLoggable(Level.FINEST);
	if (isTraceOn) {
		LOGGER.entering(LOG_CLASS, method, new Object[] { namespaceURI, 
localName, qName, atts });
	}
	try {
		if (FOLDER_TAG.equals(localName)) {
			Folder folder = new Folder(atts.getValue("", ID_ATTRIBUT), 
atts.getValue("", NAME_ATTRIBUT), atts.getValue("", DESC_ATTRIBUT));

			if (parentFolderStack.empty()) {
				createRootFolder(getBcOID(), folder);
			} else {
				addFolder(getBcOID(), parentFolderStack.peek(), 
folder.getID(), folder.getName(), folder.getDesc());
			}
			parentFolderStack.push(folder);
		} else if (FILE_TAG.equals(localName)) {
			File file = new File(atts.getValue("", ID_ATTRIBUT), 
atts.getValue("", NAME_ATTRIBUT), atts.getValue("", DESC_ATTRIBUT), 
atts.getValue("", MIME_TYPE_ATTRIBUT));
			addFile(getBcOID(), parentFolderStack.peek(), file.getID(), 
file.getName(), file.getDesc(), null);
			setHandleToFileIdMap(getBcOID(),atts.getValue("", 
FILE_HANDLE_ATTRIBUT), atts.getValue("", ID_ATTRIBUT));
		}
	} catch (PersisterException e) {
		throw new SAXException(e);
	} catch (DocLibException e) {
		throw new SAXException(e);
	}
	if (isTraceOn) {
		LOGGER.exiting(LOG_CLASS, method);
	}
}

The code checks if the element represents a folder or a document and creates the appropriate library entry on the back end. Furthermore, it retrieves and retains the mappings for every file to its file handle. This data is processed in the finishDeserializeInstance method.

As explained earlier, CAI provides a mechanism to store contents in different files. This can be utilized by a business component if it implements the StreamBackupServiceClient interface.

The document library registers each handle at the StreamBackupService within the finishDeserializeInstance method (Listing 16).

Listing 16. Register file handles at StreamBackupService
public void finishDeserializeInstance(Result result) throws ComponentException {
	final String method = "finishDeserializeInstance";
	final boolean isTraceOn = LOGGER.isLoggable(Level.FINEST);
	if (isTraceOn) {
		LOGGER.entering(LOG_CLASS, method);
	}

	if (result instanceof SAXResult) {
		if (isTraceOn) {
			LOGGER.log(Level.FINEST, "Finish Restoring DocLib");
		}
		try {
			// restore files
		      RestoreHandler restoreHandler = ((RestoreHandler) (
((SAXResult) result).getHandler()));						
			StreamBackupService streamBackupService = (StreamBackupService) 
((List) Util.getContext().lookup(StreamBackupService.JNDI_NAME)).get(0);
			Set<String> handles = getHandleOfFiles(
restoreHandler.getBcOID());  
			for (String handle : handles) {
					streamBackupService.registerRestoreData(
restoreHandler.getParameters().get(BACKUP_CONTEXT), handle);
			}
		} catch (Exception e) {
			throw new ComponentException(e);
		}
	}

	if (isTraceOn) {
		LOGGER.exiting(LOG_CLASS, method);
	}
}

The restoreData method of the StreamBackupServiceClient is called by CAI for each handle, and an InputStream to the file is passed as a parameter to the method (Listing 17).

Listing 17. Read document content from InputStream
public void restoreData(InputStream in, String componentID, String backupHandle) 
throws ComponentException {
	final String method = "restoreData";
	final boolean isTraceOn = LOGGER.isLoggable(Level.FINEST);
	if (isTraceOn) {
		LOGGER.entering(LOG_CLASS, method, String.format("componentID: %s  | 
backupHandle: %s", componentID, backupHandle));
	}
	try {
		ObjectID bcOID = Util.getIdentification().deserialize(componentID);
		String fileID = getFileIdForHandle(bcOID,backupHandle);
		insertFile(bcOID, fileID, in);
		removeHandleToFileIdMap(bcOID,backupHandle);
	} catch (SerializationException e) {
		throw new ComponentException(e);
	} catch (DocLibException e) {
		throw new ComponentException(e);
	} catch (PersisterException e) {
		throw new ComponentException(e);
	}
	if (isTraceOn) {
		LOGGER.exiting(LOG_CLASS, method);
	}
}

Within this method, the file is read from the InputStream and stored on the back end.

Finally the activate method is called. After this, the restored business component is ready for usage.

Policy

WebSphere Portal provides policies that determines how portal resources function. Policies are applied to composite applications. CAI evaluates the policy status of every composite application. The policy status of an application is determined by validating the application size, the date the application was last accessed, and the date it was last modified against the corresponding policy. These values are denoted as Sensor data elements. The sensor data of a composite application is aggregated from sensor data of business component instances.

The determination of the sensor data can be an expensive operation for a business component. For example, in the imperfect implementation of the document library, the calculation of the document library's size requires the retrieval of all files that are part of the library from the database to determine file sizes.

CAI offers a mechanism to fetch the sensor data at certain times (pull mechanism). This is done by a task (CAI policy handler) that runs periodically. This task should be scheduled at times when the additional load does not affect the performance of the portal server. Consequently any changes on applications between two runs of the policy handler are not immediately reflected in the sensor data and results in the application’s policy status to not be up-to-date.

Business components should always provide sensor data, as this improves the accuracy of the policy status of an application. Sensor data elements that imply expensive operations should be provided by implementing the Sensor interface; otherwise, sensor data should be set directly in the metadata of the corresponding business component service.

To enable more accurate policy status reporting, a business component can invoke CAI directly to update its sensor data (push mechanism). Therefore, it sets sensor data for particular metadata entries via the BusinessComponentService.

To support the pull mechanism, a business component must implement the Sensor interface. CAI invokes the getSensorData method of the business component handler and gets a collection of SensorData instances.

As you can see in Listing 18, the document library returns only its size through the Sensor interface implementation.

Listing 18. Provide document library size
public ListModel<SensorData> getSensorData(ObjectID bcOID, ListModel<SensorData> 
sensorDataList) throws ComponentException {
	final String method = "getSensorData";
	final boolean isTraceOn = LOGGER.isLoggable(Level.FINEST);
	if (isTraceOn) {
		LOGGER.entering(LOG_CLASS, method);
	}
	Iterator it;
	try {
		it = sensorDataList.iterator();
		while (it.hasNext()) {
			SensorData data = (SensorData) it.next();
			if (SensorDataConstants.SIZE_SENSOR_DATA_NAME.equals
(data.getName())) {
				Integer size = getSize(bcOID); 
					// size returned in megabytes
				data.setValue(size);
				break;
			}
		}
	} catch (ModelException e) {
		throw new SensorDataListModelException(e);
	} catch (PersisterException e) {
		throw new SensorDataListModelException(e);
	} catch (DocLibException e) {
		throw new SensorDataListModelException(e);
	}
	if (isTraceOn) {
		LOGGER.exiting(LOG_CLASS, method);
	}
	return sensorDataList;
}

The document library also provides information about the point in time of its last modification. This information is pushed to CAI. Consequently, we do not need to store the information in the back end, but can push it to CAI as our last modification point in time whenever a change is done in the library.

Listing 19 shows how this is performed in the sample.

Listing 19. Push sensor data to CAI
privatevoid updateLastModified(ObjectID bcOID) throws ApplicationException, 
NamingException, DocLibException {
	String lastModified;
	synchronized (UTC_DATE_METADATA_FORMAT) { 
             //SimpleDateFormat is not thread-safe
		lastModified=UTC_DATE_METADATA_FORMAT.format(new Date());
	}
		
	BusinessComponentService bcService = getBcsHome().
getBusinessComponentService(bcOID);		
		bcService.setMetaData(
Constants.BUSINESS_COMPONENT_METADATA_SENSOR_LASTMODIFIED,lastModified );
}

The updateLastModified method is called whenever a document or folder is created or deleted. The current date is retrieved, formatted, and set as metadata to the BusinessComponentService. CAI retains sensor data as metadata of a business component. The next time the policy status is requested by a user or the policy task, this metadata is taken on an evaluation basis and enables a more precise determination of the policy status.

As shown in Listing 19, the BusinessComponentService is retrieved from a BusinessComponentServiceHome object. It is good practice to load the home object once and reuse it to avoid additional expensive JNDI lookups. The sample code implements this lazy load pattern (Listing 20). The ApplicationService is retrieved analogously.

Listing 20. Get home instance of business component service
public static synchronized BusinessComponentServiceHome getBcsHome() throws 
NamingException, DocLibException {
	if (bcsHome == null)
		bcsHome = (BusinessComponentServiceHome) Util.getContext().lookup
(BusinessComponentServiceHome.JNDI_NAME);
	return bcsHome;
}

Community and domain roles

A composite application includes a community of members that are assigned to community roles. Only members of this community are permitted to use the composite application (an exception are the portal administrators who have special rights on applications). To define different access rights to the application, the members are organized in various community roles.

A business component can take part in this role-based approach by defining its own roles, domain roles. A community role can be mapped for each business component in the application to a particular domain role. Each domain role can be mapped to one or more community roles. These mappings are defined in the template of a composite application. They can also be customized at run time.

A business component registers its domain roles during the instantiation phase using the community APIs.

Main entry points in the community API are:

  • CommunityLocator offers functions to retrieve information about community roles, domain roles, and membership.
  • CommunityController provides functions to modify community roles, domain roles, and membership assignments.
  • CommunityObjectFactory provides functions to create filled Localized, LocalizedDomainRole, and LocalizedCommunityRole objects that can be used as input parameters for the Community API method calls.

Within the sample code, the registration of the domain roles is done in the Lifecycle method activate, which calls the createDomainObjectWithRoles method (Listing 21).

Listing 21. Register domain roles
privatevoid createDomainObjectWithRoles(ObjectID cmtyOID, ObjectID bcOID) 
throws ComponentException {
	final String method = "createDomainObjectWithRoles";
	final boolean isTraceOn = LOGGER.isLoggable(Level.FINEST);
	if (isTraceOn) {
		LOGGER.entering(LOG_CLASS, method);
	}
	Set<DomainRole> createdDomainRoles = null;
	try {
		Set<LocalizedDomainRole> domainRoles = createDomainRoles();
		createdDomainRoles = getCommunityHome().getController().
addDomainObjectToCommunity(cmtyOID, bcOID, domainRoles);
	} catch (MissingAccessRightsException e) {
		throw new ComponentException(e);
	} catch (CommunityException e) {
		throw new ComponentException(e);
	} catch (NamingException e) {
		throw new ComponentException(e);
	} catch (DocLibException e) {
		throw new ComponentException(e);
	}
	if (isTraceOn) {
		LOGGER.exiting(LOG_CLASS, method, createdDomainRoles);
	}
}

The CommunityController is used to add the domain roles to a specific community in the addDomainObjectToCommunity method. The domain roles are created in the createDomainRoles method (Listing 22).

Listing 22. Create LocalizedDomainRole instances
private Set<LocalizedDomainRole> createDomainRoles() {
	Set<LocalizedDomainRole> domainRoles = new HashSet<LocalizedDomainRole>();
	LocalizedDomainRole domainRole = chome.getObjectFactory().
createLocalizedDomainRoleObject(EDITOR_ROLE, editorRoleLocData, true);
	domainRoles.add(domainRole);
	domainRole = chome.getObjectFactory().
createLocalizedDomainRoleObject(READER_ROLE, readerRoleLocData, false);
	domainRoles.add(domainRole);
	return domainRoles;
}

The document library defines two domain roles: Editors are permitted to modify the contents of the document library, whereas Readers can only view the contents.

The CommunityObjectFactory is used to create the localized domain role instances. Each domain role gets a distinct role name. As the localized data of a domain role (titles and descriptions) is the same for all domain role instances, this data is generated once in the static method createLocalizedRoleDate (Listing 23). This method is invoked once during the static initialization of the business component handler class.

Listing 23. Create LocalizedObject instances for domain roles
private static void createLocalizedRoleDate() {
	// Localized data - at least one title is needed
	// Editor role
	HashMap<Locale, String> mapOfTitles = new HashMap<Locale, String>();
	HashMap<Locale, String> mapOfDescriptions = new HashMap<Locale, String>();
	mapOfTitles.put(Locale.ENGLISH, "Editor");
	mapOfTitles.put(Locale.GERMAN, "Schreiber");
	mapOfDescriptions.put(Locale.ENGLISH, "Editors of the document library");
	mapOfDescriptions.put(Locale.GERMAN, "Bearbeiter der Dokumentenbibliothek");
	editorRoleLocData = chome.getObjectFactory().
createLocalizedObject(mapOfTitles, mapOfDescriptions);

	// Reader role
	mapOfTitles = new HashMap<Locale, String>();
	mapOfDescriptions = new HashMap<Locale, String>();
	mapOfTitles.put(Locale.ENGLISH, "Reader");
	mapOfTitles.put(Locale.GERMAN, "Leser");
	mapOfDescriptions.put(Locale.ENGLISH, "Reader of the document library");
	mapOfDescriptions.put(Locale.GERMAN, "Leser der okumentenbibliothek");
	readerRoleLocData = chome.getObjectFactory().
createLocalizedObject(mapOfTitles, mapOfDescriptions);
}

During instantiation of a composite application, community roles are mapped to domain roles. Through the mapping of community roles to domain roles, it is guaranteed that a user of a composite application is assigned to the mapped domain roles of an included business component.

When a user works with the business component, the business component code is able to determine which domain role the user belongs to by calling the CommunityLocator method isUserInDomainRole.

In the document library sample, only users in the Editor role are permitted to add or remove documents or folders. The portlet code calls the isUserInRole method to figure out if the current user is an Editor role (Listing 24).

Listing 24. Check if current user is an Editor
public boolean isUserInRole(String bcID, String roleName) throws DocLibException {
	final String method = "isUserInRole";
	final boolean isTraceOn = LOGGER.isLoggable(Level.FINEST);
	if (isTraceOn) {
		LOGGER.entering(LOG_CLASS, method, roleName);
	}
	boolean result = true;
	try {
		ObjectID bcOID = Util.getIdentification().deserialize(bcID);
		ObjectID appOID = getAcsHome().getApplicationCatalogService().
findApplicationByContainedBC(bcOID);
		ObjectID cmtyID = getAsHome().getApplicationService(appOID).
getCommunityID();
		String componentJNDIName = DOC_LIB_BC_HANDLER_JNDI_NAME;
		result = getCommunityHome().getLocator().isUserInDomainRole(cmtyID, bcID, 
componentJNDIName, roleName);
	} catch (CommunityException e) {
		throw new DocLibException(e);
	} catch (SerializationException e) {
		throw new DocLibException(e);
	} catch (ObjectNotFoundException e) {
		throw new DocLibException(e);
	} catch (ApplicationDataException e) {
		throw new DocLibException(e);
	} catch (NamingException e) {
		throw new DocLibException(e);
	} catch (UnknownApplicationException e) {
		throw new DocLibException(e);
	} catch (AccessControlException e) {
		throw new DocLibException(e);
	}
	if (isTraceOn) {
		LOGGER.exiting(LOG_CLASS, method, result);
	}
	return result;
}

If the user is an Editor, then buttons to add folder and documents are rendered in the portlet UI, as seen in the excerpt of the portlet jsp DocLibView.jsp (Listing 25).

Listing 25. Display buttons for Editors
//is current user is in Editor role then the action buttons are displayed
if (isEditor) {
%>
<div class="lotusActionBar lotusBtnContainer" id="docLibActionButtons">
<span class="lotusBtn lotusBtnAction lotusLeft">
<a href="javascript:<portlet:namespace />hideDiv('docLibFileDialog'); 
<portlet:namespace/>centerOnElement('docLibActionButtons','docLibFileDialog'); 
<portlet:namespace/>showDiv('docLibFileDialog');">
Add Document</a>
</span>
<span class="lotusBtn lotusBtnAction lotusLeft">
<a href="javascript:<portlet:namespace />hideDiv('docLibFolderDialog'); 
<portlet:namespace/>centerOnElement('docLibActionButtons','docLibFolderDialog'); 
<portlet:namespace/>showDiv('docLibFolderDialog');">
Create Folder</a>
</span>
</div>
<%
}
%>

Membership

Business components can be notified when a principal is assigned to a domain role or when the assignment is removed. To accomplish this, the component must implement the MembershipListener SPI's methods memberAdded and memberRemoved.

When a principal gets added to a community role that had been previously mapped to a domain role of the component, the component's memberAdded method is invoked and the affected domain role (as well as the principal added) are being passed. The memberRemoved method is invoked in an analog manner after a principal is removed from that community role. These notifications enable the component to keep track of principal role mapping. This is required when the component manages access to resources separate from PAC.


Conclusion

The WebSphere Portal Composite Application Infrastructure (CAI) enables the creation and management of composite applications and their communities. It offers features like creating backups from composite applications, and supports role management on an application basis. Customers can plug in business components by implementing various CAI SPIs. CAI also provides APIs that can be used by business components and other code to retrieve information about the application or to modify it.

This article offered some insight into the lifecycle of a business component and the ways it can be deployed, including how a business component can take part in the backup and restore process. Different ways a business component can provide sensor data were explained to enable CAI to determine if a composite application exceeds a policy threshold setting. Additionally, the article showed how and when a business component can create domain roles, and what APIs can be used to test when a user is assigned to a certain domain role.

Also included were recommendations for the implementation of a business component and sample code to further illustrate how an implementation might look. The full sample source is included with this article for demonstration purposes.


About the download materials

After uncompressing the archive, the files you will find include:

  • Src directory that contains the full source code of the document library
  • JavaDoc directory that includes the Javadoc of the sample.

Download

DescriptionNameSize
Code sampleCAI_BC_Sample-B.zip3.4MB

Resources

Learn

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into WebSphere on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=WebSphere, Lotus
ArticleID=578472
ArticleTitle=Building a business component for the WebSphere Portal composite application infrastructure
publish-date=11102010