Construindo um componente de negócios para a infraestrutura de aplicativo composto do WebSphere Portal

Este artigo fornece uma visão geral de como escrever um componente de negócios para a infraestrutura de aplicativo composto no IBM ® WebSphere® Portal. Ele inclui uma amostra de implementação de um componente de negócios que demonstra como a infraestrutura de aplicativo composto pode ser usada, além de melhores práticas a aproveitar e armadilhas a evitar. Este conteúdo é parte do IBM WebSphere Developer Technical Journal.

Izidor Jager, Software Developer, IBM

Izidor Jager é desenvolvedor de software no IBM Development Laboratory em Boeblingen. Ele entrou para a equipe de desenvolvimento do WebSphere Portal em 2005. Sua área principal é a Composite Application Infrastructure. Izidor recebeu o diploma em Ciência da Computação da Universidade de Stuttgart.



Sven Stueven, Software Developer, IBM

Sven Stueven é desenvolvedor de software no IBM Development Laboratory em Boeblingen. Ele entrou para a equipe de desenvolvimento do WebSphere Portal em 2009, depois de trabalhar três anos em serviços com base em laboratório. Sven recebeu o diploma e Informática de Negócios da Universidade de Ciências Aplicadas de Karlsruhe.



21/Dez/2010

Introdução

A infraestrutura de aplicativo composto (CAI) do IBM WebSphere Portal permite a criação e o gerenciamento de aplicativos compostos. No WebSphere Portal, um aplicativo composto é composto de artefatos do WebSphere Portal, como páginas, portlets e uma comunidade. Uma comunidade define funções que fornecem direitos de acesso e membros que pertencem a uma ou mais funções.

Adicionalmente, um aplicativo composto pode conter componentes de negócios customizados. Componentes de negócios estendem a infraestrutura e fornecem gerenciamento de dados em sistemas de backend. Na maioria das vezes, um portlet aproveita um componente de negócios para trabalhar com dados de um backend específico. Por exemplo, um portlet poderá usar um componente de negócios para gerenciar documentos em uma biblioteca de documentos.

As seções a seguir explicam mais sobre a CAI, que SPIs (interfaces com o provedor de serviço) e APIs são fornecidas, como essas SPIs podem ser implementadas e como as APIs podem ser usadas. Como um componente de negócios é implementado no WebSphere Portal também é explicado.

Um exemplo de implementação de componente de negócios está incluído neste artigo. A amostra de componente de negócios é uma biblioteca de documentos. A partir de agora, o termo biblioteca de documentos fará referência a esta amostra de componente de negócios.

O código de origem e o Javadoc da amostra de biblioteca de documentos estão disponíveis para download, juntamente com um guia do usuário explica a amostra em detalhes. Além disso, ele mostra a funcionalidade da biblioteca de documentos e como ela usa os recursos de CAI.


Interfaces da CAI

Para ativar a CAI para interagir com um componente de negócios, o código do componente de negócios precisa incluir uma classe de manipulador de componente de negócios que implementa as SPIs da CAI. Isto permite que a CAI chame os componentes de negócios quando certos eventos ocorrerem; por exemplo, se um aplicativo é serializado em um modelo ou um usuário é adicionado a um aplicativo.

Essas são as interfaces que um manipulador de componente de negócios pode implementar:

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

Elas podem ser encontradas (exceto o último item) no pacote com.ibm.portal.app.component2. A última está localizada em com.ibm.portal.app.service.backup. O Javadoc para essas interfaces está disponível dentro de uma instalação do WebSphere Portal no diretório /PortalServer/doc/Javadoc/api_docs/.

Exceto por Lifecycle, todas as outras interfaces são opcionais. O conjunto de interfaces que o manipulador do componente de negócios precisa para implementar depende dos recursos com os quais o componente de negócios quiser se integrar. Por exemplo, se um componente de negócios desejar se integrar com a capacidade de backup da CAI, ele deverá implementar a interface Serializable.

Sempre que um componente de negócios não puder realizar um retorno de chamada com sucesso, é uma boa prática lançar uma com.ibm.portal.app.exceptions.ComponentException. Isto permite que a CAI inicie uma ação apropriada; por exemplo, excluir um componente de negócios que não foi inicializado com sucesso.

Todos os métodos de retorno de chamada tem em comum o primeiro parâmetro como o identificador de uma instância particular de um componente de negócios. Portanto, um manipulador de componente de negócios sabe que instância concreta do componente de negócios deverá ser processada. O identificador é sempre uma instância do WebSphere Portal class com.ibm.portal.ObjectID e é criado pela CAI.

Além de SPIs, a CAI também oferece APIs. Algumas que valem a pena mencionar incluem BusinessComponentService e ApplicationService. BusinessComponentService, por exemplo, pode ser usada para armazenar dados na CAI sobre um componente de negócios que não está refletido no backend. E ApplicationService, por exemplo, pode ser usada para obter informações sobre outros componentes de negócios de um aplicativo.

Em breve, você verá como a amostra do componente de negócios usa a API da CAI.


Implementando um componente de negócios da CAI

A CAI usa a estrutura de ponto de extensão Eclipse, que é integrada no IBM WebSphere Application Server, para recuperar o manipulador de um componente de negócios. Portanto, a CAI define um ponto de extensão com o ID do plugin com_ibm_portal_app.BusinessComponents. Um componente de negócios deve ser uma extensão deste ponto de extensão.

Para registrar um componente de negócios no ponto de extensão, um arquivo plugin.xml file deve ser fornecido. O arquivo plugin.xml da biblioteca de documentos é exibido na Listagem 1.

Listagem 1. Plugin.xml para a biblioteca de documentos
<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>

A extensão com ID ai.docLib.sample.DocLibHandler define que classe Java no componente de negócios é a classe manipuladora do componente de negócios. O nome completo de classe é o valor do atributo class no elemento provider. A classe manipuladora do componente de negócios da biblioteca de documentos é com.ibm.wps.ai.sample.doclib.handler.DocLibBCHandlerImpl. Como mencionado anteriormente, a CAI usa este manipulador para interagir com o componente de negócios.

Se um componente de negócios for registrado na estrutura de extensão, o manipulador poderá ser acessado por uma consulta do JNDI. O nome de ligação JNDI de um componente de negócios da CAI mapeia para este padrão:

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

Para a biblioteca de documentos, ele é: portal:extreg/ai.docLib.sample.DocLibSampleHandler.

Se um portlet quiser usar um componente de negócios, ele pode informar à CAI sobre isto fornecendo uma preferência com o nome com.ibm.portal.bc.ref. A referência é dada na forma de um nome JNDI. Isto permite que a CAI crie uma instância do componente de negócios assim que o portlet for adicionado a um aplicativo.

O portlet.xml para a biblioteca de documentos contém a preferência exibida na Listagem 2.

Listagem 2. Preferência do portlet que define o componente de negócios
<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>

A forma recomendada de empacotar o portlet e o componente de negócios é empacotá-los em um archive de aplicativo de portlet e implementar o aplicativo de portlet no WebSphere Portal. O componente de negócios é, então, automaticamente registrado no registro de extensões e acessível pela CAI.


Ciclo de vida de um componente de negócios

A CAI contém um contêiner de componente de negócios para gerenciar o ciclo de vida dos componentes de negócios. Considere essas cinco fases no ciclo de vida de um componente de negócios:

  1. criado
  2. ativado
  3. pré-excluído
  4. excluído
  5. pós-excluído

Quando um componente de negócios é instanciado, ele é primeiro criado e, posteriormente, ativado. Essas fases são refletidas nos métodos da interface Lifecycle create e activate:

  • Create é chamado no início da fase de instanciação e aciona a criação de uma nova instância do componente e a inicialização dos recursos necessários.
  • Activate está no final da fase de instanciação para ativar o componente de negócios para uso.

O motivo para as duas fases é unificar as diferentes formas pelas quais é possível criar um componente de negócios. Um componente de negócios pode ser criado no tempo de execução de um aplicativo ou durante a instanciação de um aplicativo a partir de um modelo de aplicativo ou backup. A instanciação segue o padrão: criar um componente de negócios vazio, preenchê-lo com dados e, a seguir, ativá-lo.

A Listagem 3 mostra o método create de Lifecycle da biblioteca de documentos. Ele somente cria a pasta raiz da biblioteca.

Listagem 3. Criar componente de negócios
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);
	}
}

O que deve ser retido pelo manipulador do componente de negócios é o identificador de um componente de negócios concreto. O identificador é criado pela CAI e passado como o primeiro parâmetro no método create. Em todos os retornos de chamada da CAI subsequentes, este identificador será passado para identificar qual instância concreta do componente de negócios é afetada. O código do componente de negócios deve ser capaz de mapear este identificador a seus dados de backend. A biblioteca de documentos armazena o identificador serializado do componente de negócios em seu backend; uma coluna na tabela correspondente é reservada para isto. Em vez de armazenar o identificador no backend, ele também pode ser depositado na CAI. O BusinessComponentService pode ser usado para armazenar dados para um componente de negócios.

O método ativo da biblioteca de documentos (Listagem 4) registra suas funções no componente portal access control (PAC) do WebSphere Portal. Como você pode ver, o ApplicationCatalogService é usado para obter o identificador do aplicativo composto ao qual o componente de negócios pertence. O ApplicationService é usado para determinar o identificador da comunidade, que é necessário quando uma função é registrada no PAC.

Listagem 4. Ativar componente de negócios
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);
	}
}

É uma boa prática definir e registrar funções de domínio na fase de ativação de um componente de negócios.

Se um componente de negócios for destruído, existem três fases: Primeiro o componente de negócios é pré-excluído, depois excluído e, finalmente, pós-excluído. Essas fases são refletidas nos métodos da interface Lifecycle preDelete, deletee postDelete. Para entender o significado dessas fases, vamos ver o que significa excluir um aplicativo composto.

Quando um aplicativo composto é excluído, ele não é inteiramente excluído, mas simplesmente sinalizado como excluído, e o acesso ao aplicativo é proibido. Isto ocorre porque a exclusão de um aplicativo composto pode demorar algum tempo. A CAI permite adiar as partes demoradas da exclusão para um momento em que a carga do sistema não esteja em níveis altos. Isto é obtido por uma tarefa especial da CAI, o application purger, que é executado em um momento customizável e, no final, exclui os aplicativos sinalizados como excluídos.

Para suportar a separação de partes mais e menos demoradas do processo de exclusão, essas diferentes fases da exclusão no ciclo de vida de um componente de negócios foram criadas.

O método preDelete é chamado quando um aplicativo é sinalizado como excluído pela CAI. O componente de negócios deverá executar ações que não podem ser adiadas e devem ser realizadas imediatamente. Por exemplo, o componente de negócios poderá revogar acesso aos dados do backend.

Ações que podem ser adiadas -- idealmente, as demoradas -- são movidas nas fases quando o aplicativo é finalmente excluído pelo application purger da CAI. Então, os métodos delete e postDelete são chamados.

Na implementação da biblioteca de documentos, o método preDelete está vazio porque não há nada a ser feito imediatamente (Listagem 5).

Listagem 5. Pré-exclusão do componente de negócios
public void preDelete(ObjectID bcOID) throws ComponentException {
		// nothing to do	}

A exclusão da biblioteca de documentos é adiada para o método delete (Listagem 6).

Listagem 6. Excluir o componente de negócios
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);
	}
}

Não é necessário remover o registro das funções de domínio no PAC quando um componente de negócios é destruído. Isto é tratado pela CAI automaticamente.

A biblioteca de documentos é excluída do banco de dados.

Como a exclusão da biblioteca de documentos é a única ação a ser realizada quando o componente de negócios da biblioteca de documentos for destruído, não há nada remanescente para o método postDelete e, portanto, ele está vazio (Listagem 7).

Listagem 7. Pós-exclusão do componente de negócios
public void postDelete(ObjectID bcOID) throws ComponentException {
	// nothing to do
}

Usando recursos da CAI

A CAI fornece recursos para backup e restauração de aplicativos e para gerenciar comunidades de aplicativos compostos e políticas de suporte. Para usar esses recursos, um componente de negócios deverá implementar as SPIs correspondentes da CAI.

As seções a seguir explicam esses recursos de forma geral e como a biblioteca de documentos implementa as SPIs em particular. O manipulador do componente de negócios para a biblioteca de documentos implementa as SPIs da CAI da forma como mostra a Listagem 8.

Listagem 8. SPIs da CAI implementadas pela biblioteca de documentos
public class DocLibBCHandlerImpl implements DocLibBCHandler, Lifecycle, Serializable, 
	Sensor, StreamBackupServiceClient, MembershipListener{
...

Serialização em um backup

Um aplicativo composto pode ser serializado de duas formas: em um modelo de aplicativo composto (um projeto de aplicativo composto) e em um backup. Um backup contém funções, membros da comunidade, dados do aplicativo e, normalmente, os dados do backend dos componentes de negócios.

Para participar do processo de serialização, um manipulador de componente de negócios deverá implementar a interface Serializable. Um componente de negócios pode suportar diferentes tipos de serialização. Ele declara que tipos suporta por meio do método supportsSerialization. A biblioteca de documentos suporta ambos, serialização para um modelo e para um backup (Listagem 9).

Listagem 9. A biblioteca de documentos suporta serialização para um modelo e para um backup
publicboolean supportsSerialization(SerializationType type) {
		return ((SerializationType.TEMPLATE.equals(type)) || 
			(SerializationType.BACKUP.equals(type)));			
}

A serialização para um modelo funciona quase da mesma forma, mas os dados armazenados são diferentes. Enquanto que um backup armazena exatamente os dados disponíveis em uma instância, o modelo é projetado para dados que toda nova instância do aplicativo criada a partir do modelo deverá conter.

Quando um backup é realizado, a CAI verifica quais componentes de negócios que fazem parte do aplicativo suportam backups. Para conseguir isto, a CAI primeiro avalia se um componente de negócios implementa a interface Serializable. Depois disso, verifica se o componente suporta backup por meio do método supportSerialization (Listagem 9). Se este for o caso, o método serializeInstance do manipulador do componente de negócios é chamado. Como este método é chamado pela CAI para todos os tipos de serialização, o tipo de serialização é passado no método.

A CAI espera que o componente de negócios retorne uma fonte de entrada derivada da interface javax.xml.transform.Source. Ela pode ser DOMSource, SAXSource ou StreamSource.

Do ponto de vista de desempenho, é melhor fornecer uma SAXSource quando houver uma grande quantidade de dados de backend, pois as outras implementações exigem que os dados sejam mantidos em memória por algum tempo.

A biblioteca de documentos retorna uma SAXSource (Listagem 10) no caso de um backup porque uma biblioteca de documentos pode conter muitos dados. Para serialização em um modelo, ela retorna uma DOMSource.

Listagem 10. Serialização da biblioteca de documentos retorna instância da origem
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;
	}

O BackupSerializationXMLReader é passado para o construtor da SAXSource como um XMLReader. Este leitor cria eventos SAX transformando o modelo da biblioteca de documentos em uma representação XML. Isto é feito no método sendFolder (Listagem 11) no BackupSerializationXMLReader. A CAI fornece um ContentHandler para receber os eventos SAX. Os eventos são então serializados em XML e armazenados em um arquivo chamado appliction-instance.xml, que é parte do backup.

Listagem 11: Criar eventos SAX para as entradas da biblioteca de documentos
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);
	}
}

É possível serializar todos os dados do backend desta forma. Consequentemente, todos os dados do backend são gravados em application-instance.xml. Isto poderia, no entanto, resultar em um arquivo muito grande. Em contraste com a serialização em modelo, a CAI fornece um mecanismo para a serialização em backup para gravar dados em arquivos adicionais, além do arquivo application-instance.xml.

Grave grandes quantidades de dados em arquivos separados.

A biblioteca de documentos usa este mecanismo para armazenar o conteúdo de cada documento em um arquivo separado. Isto é obtido implementando a interface StreamBackupServiceClient. Durante o processo de serialização, o manipulador do componente de negócios registra cada arquivo no StreamBackupService passando o identificador de arquivo. O serviço retorna um identificador de arquivos, como pode ser visto na Listagem 12.

Listagem 12. Registro de cada arquivo no 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));

O identificador de arquivos é mapeado para o identificador de arquivo e exposto na application-instance.xml. Estes dados são necessários durante a restauração para mapear o conteúdo do arquivo para um documento no modelo da biblioteca de documentos.

Para serializar o conteúdo do arquivo, a CAI chama o método backupData no manipulador do componente de negócios (Listagem 13).

Listagem 13. Serializar conteúdo de documento em arquivo separado
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);
		}
	}

O parâmetro handback é o identificador do arquivo que foi passado para StreamBackupService dentro do método serializeInstance.

Quando a ação de backup é concluída, o backup contém um arquivo separado para cada documento da biblioteca de documentos.

Restauração de um backup

Um aplicativo composto pode ser desserializado de duas formas: a partir do modelo de um aplicativo composto ou a partir de um backup. O fluxo de controle básico é o mesmo para ambos os tipos. Durante o processo de desserialização, todos os componentes de negócios que pertencem ao aplicativo composto são criados e preenchidos com dados.

Quando um aplicativo composto que já existe é restaurado, ele é primeiro excluído e, a seguir, criado novamente. Consequentemente, todos os componentes de negócios incluídos precisam ser recriados.

Quando uma restauração é realizada, a CAI verifica quais componentes de negócios no aplicativo suportam o recurso de restauração. Isto funciona da mesma forma que quando um backup é realizado: a CAI primeiro verifica se um componente de negócios implementa a interface Serializable. Depois disso, ela verifica se o componente suporta backup por meio do método supportSerialization (Listagem 9).

Preencher um componente de negócios com dados é feito em três fases:

  1. A CAI chama o método initDeserializeInstance do manipulador do componente de negócios para obter um destino para os dados do componente de negócios.
  2. A seguir, envia os dados XML para o objeto Result fornecido.
  3. Finalmente, o método finishDeserializeInstance é chamado.

Esta etapa final é, na verdade, necessária somente quando o componente não usar um SAXResult, pois o componente poderá detectar, caso contrário, o final do fluxo de dados por meio do método endDocument. No caso de uma exceção durante o processo, o método cancelDeserializeInstance do componente é chamado para que ele possa remover qualquer coisa que tenha sido criada até então.

Novamente, a CAI chama esses métodos do manipulador do componente de negócios nesta sequência:

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

Os métodos create e activate foram explicados anteriormente. O método initDeserializeInstance retorna um destino para os dados do componente de negócios. A CAI espera que o componente de negócios retorne um destino que seja derivado da interface javax.xml.transform.Result. Ela pode ser DOMResult, SAXResult ou StreamResult.

Do ponto de vista de desempenho, é melhor fornecer uma SAXResult quando houver uma grande quantidade de dados de backend, pois as outras implementações exigem que os dados sejam mantidos em memória por algum tempo.

A biblioteca de documentos retorna um SAXResult no caso de uma restauração (Listagem 14). Para a desserialização a partir de um modelo, ela retorna um DOMResult para fins de demonstração.

Listagem 14. Destino de retorno para os dados serializados
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;
}

O SAXResult retornado é inicializado com uma implementação de org.xml.sax.ContentHandler chamado RestoreHandler que é chamado pela CAI durante a análise da representação serializada do componente de negócios.

Por exemplo, quando o início de um elemento XML for detectado durante a análise, a CAI chama startElement no RestoreHandler (Listagem 15).

Listagem 15. Restauração de entradas da biblioteca de documentos quando o evento SAX ocorre
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);
	}
}

O código verifica se o elemento representa uma pasta ou um documento e cria a entrada de biblioteca apropriada no backend. Além disso, ele recupera e retém os mapeamentos para cada arquivo em seu identificador de arquivos. Esses dados são processados no método finishDeserializeInstance.

Como explicado anteriormente, a CAI fornece um mecanismo para armazenar conteúdo em diferentes arquivos. Isto pode ser utilizado por um componente de negócios se ele implementar a interface StreamBackupServiceClient.

A biblioteca de documentos registra cada identificador no StreamBackupService dentro do método finishDeserializeInstance (Listagem 16).

Listagem 16. Registro dos identificadores de arquivos no 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);
	}
}

O método restoreData do StreamBackupServiceClient é chamado pela CAI para cada identificador e um InputStream para o arquivo é passado como um parâmetro para o método (Listagem 17).

Listagem 17. Leitura do conteúdo do documento a partir do 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);
	}
}

Dentro deste método, o arquivo é lido do InputStream e armazenado no backend.

Finalmente, o método activate é chamado. Depois disto, o componente de negócios restaurado está pronto para o uso.

Política

O WebSphere Portal fornece políticas que determinam como os recursos do portal funcionam. As políticas são aplicadas a aplicativos compostos. A CAI avalia o status da política de cada aplicativo composto. O status da política de um aplicativo é determinado validando o tamanho do aplicativo, a data em que ele foi acessado pela última vez e a data em que foi modificado pela última vez com relação à política correspondente. Esses valores são denotados como elementos de dados do Sensor. Os dados do sensor de um aplicativo composto são agregados dos dados do sensor de instâncias do componente de negócios.

A determinação dos dados do sensor pode ser uma operação cara para um componente de negócios. Por exemplo, na implementação imperfeita da biblioteca de documentos, o cálculo do tamanho da biblioteca de documentos requer a recuperação de todos os arquivos que sejam parte da biblitoeca a partir do banco de dados para determinar os tamanhos dos arquivos.

A CAI oferece um mecanismo para buscar os dados do sensor em certos momentos (mecanismo pull). Isto é feito por uma tarefa (CAI policy handler) que é executada periodicamente. Esta tarefa deverá ser programada para momentos quando a carga adicional não afetará o desempenho do servidor de portal. Consequentemente, quaisquer alterações nos aplicativos entre duas execuções do manipulador da política não são imediatamente refletidas nos dados do sensor e resulta no status da política do aplicativo estar desatualizado.

Os componentes de negócios sempre deverão fornecer dados do sensor, pois isto melhora a precisão do status da política de um aplicativo. Elementos de dados do sensor que implicam em operações caras deverão ser fornecidos implementando a interface Sensor; caso contrário, os dados do sensor deverão ser configurados diretamente nos metadados do serviço correspondente do componente de negócios.

Para possibilitar um relato de status de política mais preciso, um componente de negócios poderá chamar a CAI diretamente para atualizar seus dados de sensor (mecanismo push). Portanto, ele define os dados do sensor para entradas de metadados particulares por meio do BusinessComponentService.

Para suportar o mecanismo pull, um componente de negócios deverá implementar a interface Sensor. A CAI chama o método getSensorData do manipulador do componente de negócios e obtém uma coleção de instâncias de SensorData.

Como pode ser visto na Listagem 18, a biblioteca de documentos retorna somente seu tamanho por meio da implementação da interface Sensor.

Listagem 18. Fornecimento do tamanho da biblioteca de documentos
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;
}

A biblioteca de documentos também fornece informações sobre o momento de sua última modificação. Estas informações são enviadas para a CAI. Consequentemente, não é preciso armazenar as informações no backend, mas elas podem ser enviadas para a CAI como ponto de última modificação sempre que uma modificação for realizada na biblioteca.

A Listagem 19 mostra como isto é realizado na amostra.

Listagem 19. Enviar dados do sensor para a 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 );
}

O método updateLastModified é chamado sempre que um documento ou pasta for criado ou excluído. A data atual é recuperada, formatada e definida como metadados do BusinessComponentService. A CAI retém os dados do sensor como metadados de um componente de negócios. Na próxima vez que o status da política for solicitado por um usuário ou pela tarefa de política, estes metadados são recuperados com base em avaliação e permite uma determinação mais precisa do status da política.

Como mostra a Listagem 19, o BusinessComponentService é recuperado de um objeto BusinessComponentServiceHome. É uma boa prática carregar o objeto inicial uma vez e reutilizá-lo, para evitar consultas adicionais caras do JNDI. O código de amostra implementa este padrão de carregamento "preguiçoso" (Listagem 20). O ApplicationService é recuperado da mesma forma.

Listagem 20. Obter a instância inicial do serviço do componente de negócios
public static synchronized BusinessComponentServiceHome getBcsHome() throws 
NamingException, DocLibException {
	if (bcsHome == null)
		bcsHome = (BusinessComponentServiceHome) Util.getContext().lookup
(BusinessComponentServiceHome.JNDI_NAME);
	return bcsHome;
}

Funções de comunidade e de domínio

Um aplicativo composto inclui uma comunidade de membros que são atribuídos a to funções de comunidade. Somente membros desta comunidade têm permissão de usar o aplicativo composto (uma exceção são os administradores do portal, que têm direitos especiais sobre aplicativos). Para definir diferentes direitos de acesso ao aplicativo, os membros são organizados em várias funções de comunidade.

Um componente de negócios pode fazer parte desta abordagem com base em funções definindo suas próprias funções, funções de domínio. Uma função de comunidade pode ser mapeada para cada componente de negócios no aplicativo para uma função de domínio particular. Cada função de domínio pode ser mapeada para uma ou mais funções de comunidade. Esses mapeamentos são definidos no modelo de um aplicativo composto. Eles também podem ser customizados em tempo de execução.

Um componente de negócios registra suas funções de domínio durante a fase de instanciação usando APIs de comunidade.

Os pontos de entrada principais da API de comunidade são:

  • CommunityLocator oferece funções para recuperar informações sobre funções de comunidade, funções de domínio e associação.
  • CommunityController fornece funções para modificar funções de comunidade, funções de domínio e atribuições de associação.
  • CommunityObjectFactory fornece funções para criar objetos Localized, LocalizedDomainRole e LocalizedCommunityRole preenchidos que podem ser usados como parâmetros de entrada para as chamadas do método da API Community.

Dentro do código de amostra, o registro das funções de domínio é feito no método activate de Lifecycle, que chama o método createDomainObjectWithRoles (Listagem 21).

Listagem 21. Registro das funções de domínio
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);
	}
}

O CommunityController é usado para adicionar as funções de domínio a uma comunidade específica no método addDomainObjectToCommunity. As funções de domínio são criadas no método createDomainRoles (Listagem 22).

Listagem 22. Criação de instâncias de LocalizedDomainRole
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;
}

A biblioteca de documentos define duas funções de domínio: Editors têm permissão de alterar o conteúdo da biblioteca de documentos, enquanto que Readers só podem visualizar o conteúdo.

CommunityObjectFactory é usado para criar as instâncias localizadas de função de domínio. Cada função de domínio recebe um nome de função distinto. Como os dados localizados de uma função de domínio (títulos e descrições) são os mesmos para todas as instâncias da função de domínio, esses dados são gerados uma vez no método estático createLocalizedRoleDate (Listagem 23). Este método é chamado uma vez durante a inicialização estática da classe manipuladora do componente de negócios.

Listagem 23. Criação das instâncias de LocalizedObject para funções de domínio
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);
}

Durante a instanciação de um aplicativo composto, funções de comunidade são mapeadas para funções de domínio. Por meio do mapeamento de funções de comunidade para funções de domínio, é garantido que um usuário de um aplicativo composto seja atribuído às funções de domínio mapeadas de um aplicativo composto incluído.

Quando um usuário trabalha com o componente de negócios, o código do componente de negócios é capaz de determinar a que função de domínio o usuário pertence chamando o método isUserInDomainRole de CommunityLocator.

Na amostra da biblioteca de documentos, somente usuários na função Editor têm permissão de adicionar ou remover documentos ou pastas. O código do portlet chama o método isUserInRole para descobrir se o usuário atual é uma função Editor (Listagem 24).

Listagem 24. Verificar se o usuário atual é um 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;
}

Se o usuário for um Editor, então botões para adicionar pasta e documentos são renderizados na UI do portlet, como visto no extrato do jsp do portlet DocLibView.jsp (Listagem 25).

Listagem 25. Exibir botões para Editores
//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>
<%
}
%>

Associação

Componentes de negócios podem ser notificados quando um diretor é atribuído a uma função de domínio ou quando a atribuição é removida. Para conseguir isto, o componente deverá implementar os métodos memberAdded e memberRemoved da SPI MembershipListener.

Quando um diretor é adicionado a uma função de comunidade que foi anteriormente mapeada para uma função de domínio, o método memberAdded do componente é chamado e a função de domínio afetada (bem como o diretor adicionado) é passada. O método memberRemoved é chamado de uma forma análoga depois que um diretor é removido daquela função de comunidade. Essas notificações permitem que o componente controle o mapeamento da função de diretor. Isto é necessário quando o componente gerencia o acesso a recursos de forma separada do PAC.


Conclusão

A WebSphere Portal Composite Application Infrastructure (CAI) permite a criação e o gerenciamento de aplicativos compostos e suas comunidades. Ela oferece recursos, como a criação de backups a partir de aplicativos compostos, e suporta o gerenciamento de funções com base em um aplicativo. Os clientes podem adicionar componentes de negócios implementando várias SPIs da CAI. A CAI também fornece APIs que podem ser usadas por componentes de negócios e outros códigos para recuperar informações sobre o aplicativo ou modificá-lo.

Este artigo ofereceu algum insight sobre o ciclo de vida de um componente de negócios e as formas como ele pode ser implementado, incluindo como um componente de negócios pode fazer parte do processo de backup e restauração. Diferentes formas pelas quais um componente de negócios pode fornecer dados de sensores foram explicadas para permitir que a CAI determine se um aplicativo composto excede uma configuração de limite de política. Adicionalmente, o artigo mostrou como e quando um componente de negócios pode criar funções de domínio e que APIs podem ser usadas para testar quando um usuário é atribuído a uma certa função de domínio.

Também estão incluídas recomendações para a implementação de um componente de negócios e código de amostra para ilustrar como uma implementação se parece. A origem completa da amostra está incluída neste artigo para fins de demonstração.


Sobre os materiais de download

Depois de descompactar o archive, os arquivos que encontrará incluem:

  • Diretório Src que contém o código de origem completo da biblioteca de documentos
  • Diretório JavaDoc que inclui o Javadoc da amostra.

Download

DescriçãoNomeTamanho
Code sampleCAI_BC_Sample-B.zip3.4MB

Recursos

Aprender

Discutir

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Lotus, WebSphere
ArticleID=604422
ArticleTitle=Construindo um componente de negócios para a infraestrutura de aplicativo composto do WebSphere Portal
publish-date=12212010