sexta-feira, 20 de março de 2009

"Injetando" funcionalidades nas páginas do JBoss Portal

Este é meu segundo Post sobre JBoss Portal neste ano, e irá abordar uma forma diferenciada de "injetar" determinada funcionalidade a uma área do layout do seu site usando AOP com uma implementação de JBossInterceptor.

O JBoss Portal disponibiliza uma API de Injeção de Dependências onde é possível criar interceptadores a partir da classe abstrata JBossInterceptor. A classe ControllerInterceptor (q extends JBossInterceptor), é utilizada como base para adicionar segurança em commands, navegação, menus, header, dashboard, etc...
É possível criar seu Aspecto para o JBoss Portal herdando ControllerInterceptor e criando seu método invoke.

Para exemplificar, iremos adicionar um título ao layout do Portal utilizando o nome de cada página criado via interface administrativa em uma área pré-definida no layout utilizando o Interceptador PageCustomizerInterceptor, o qual já está sendo utilizado para outros fins. Resumindo, iremos alterar o PageCustomizerInterceptor para que a partir dele os títulos sejam incluídos dinamicamente.

O Código 1 define o MBean PageCustomizer com o atributo Title, onde é passado o Path da página que irá exibir o Título da página.

Código 1
   <mbean code="org.jboss.portal.core.aspects.controller.PageCustomizerInterceptor"
name="portal:service=Interceptor,type=Command,name=PageCustomizer" xmbean-dd=""
xmbean-code="org.jboss.portal.jems.as.system.JBossServiceModelMBean">
<xmbean/>
<attribute name="TargetContextPath">/portal-core</attribute>
<attribute name="HeaderPath">/WEB-INF/jsp/header/header.jsp</attribute>
<attribute name="TabsPath">/WEB-INF/jsp/header/tabs.jsp</attribute>
<attribute name="Breadcrumbs">/WEB-INF/jsp/header/breadcrumbs.jsp</attribute>
<attribute name="Title">/WEB-INF/jsp/header/title.jsp</attribute>
<!-- Overrides the value of core.login.namespace in config.xml --><!-- attribute name="LoginNamespace">dashboard</attribute -->
<depends optional-attribute-name="Config" proxy-type="attribute">portal:service=ServerConfig</depends>
<depends optional-attribute-name="PortalAuthorizationManagerFactory" proxy-type="attribute">
portal:service=PortalAuthorizationManagerFactory
</depends>
<depends optional-attribute-name="PortalObjectContainer" proxy-type="attribute">portal:container=PortalObject
</depends>
</mbean>

O Código 2 mostra a customização da classe PageCustomizerInterceptor onde foi criado o atributo de classe privado chamado title e seus métodos get e set. Este atributo está associado ao Title definido no MBean e será utilizado pelo método injectTitle para fazer o redirecionamento para o jsp de exibição do título. Além disso, o método injectTitle também adiciona dois atributos a requisição: org.jboss.portal.api.PORTAL_NODE e org.jboss.portal.api.PORTAL_RUNTIME_CONTEXT, com valor do Nó corrente e do contexto respectivamente. O método invoke é utilizado para montar a página com suas respectivas propriedades.

Código 2
public class PageCustomizerInterceptor extends ControllerInterceptor {
...

/** title */
private String title;

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

...

public ControllerResponse invoke(ControllerCommand cmd) throws Exception {
ControllerResponse resp = (ControllerResponse)cmd.invokeNext();

// Insert navigation portlet in the page
if (resp instanceof PageRendition) {
PageRendition rendition = (PageRendition)resp;

if (cmd instanceof PageCommand) {
PageCommand rpc = (PageCommand)cmd;

String tabbedTitle = injectTitle(rpc);
if (tabbedTitle != null) {
Map windowProps = new HashMap();
windowProps.put(ThemeConstants.PORTAL_PROP_WINDOW_RENDERER, "emptyRenderer");
windowProps.put(ThemeConstants.PORTAL_PROP_DECORATION_RENDERER, "emptyRenderer");
windowProps.put(ThemeConstants.PORTAL_PROP_PORTLET_RENDERER, "emptyRenderer");
WindowResult res = new WindowResult("", tabbedTitle, Collections.EMPTY_MAP, windowProps, null, WindowState.NORMAL, Mode.VIEW);
WindowContext blah = new WindowContext("BLAH", "tituloPrincipal", "0", res);
rendition.getPageResult().addWindowContext(blah);

//
Region region = rendition.getPageResult().getRegion2("tituloPrincipal");
DynaRenderOptions.NO_AJAX.setOptions(region.getProperties());
}
}
}

return resp;
}

...

public String injectTitle(PageCommand rpc) {
ControllerContext controllerCtx = rpc.getControllerContext();
ControllerRequestDispatcher rd = controllerCtx.getRequestDispatcher(targetContextPath, title);

if (rd != null) {
Page page = rpc.getPage();
PortalAuthorizationManager pam = portalAuthorizationManagerFactory.getManager();
PortalNodeImpl node = new PortalNodeImpl(pam, page);

rd.setAttribute("org.jboss.portal.api.PORTAL_NODE", node);
rd.setAttribute("org.jboss.portal.api.PORTAL_RUNTIME_CONTEXT", Navigation.getPortalRuntimeContext());

rd.include();
return rd.getMarkup();
}

return null;
}

...

}

O Código 3 mostra o JSP que exibe o título da página.

Código 3
<%@ page import="org.jboss.portal.api.node.PortalNode" %>
<%@ page import="java.util.Locale" %>
<%
PortalNode node = (PortalNode)request.getAttribute("org.jboss.portal.api.PORTAL_NODE");

// Get a locale
Locale locale = request.getLocale();
if (locale == null)
{
locale = Locale.getDefault();
}
%>
<%= node.getDisplayName(locale) %>

O Código 4 mostra a chamada a taglib para encaixar o título no layout desejado.

Código 4
<p:region regionName='tituloPrincipal' regionID='tituloPrincipal'/>


Considerações finais
Este post tratou uma forma flexível de inclusão de funcionalidades ao layout do JBoss Portal. Atualmente já é utilizada para outras áreas, mas, além da inclusão de títulos dinâmicos, pode ser totalmente adaptada para inclusão de breadcrumbs, por exemplo, principalmente para evitar ter que incluir seu respectivo Portlet em todas as páginas necessárias manualmente, economizando tempo precioso de desenvolvimento.

sexta-feira, 23 de janeiro de 2009

Lançamento JBossBrasil.ORG!


Na última quarta-feira, 21 de janeiro de 2009, aconteceu o lançamento da comunidade JBossBrasil.ORG. O evento foi apresentado no auditório da GlobalCode. É um grande incentivo ao aprendizado, troca de idéias e boas práticas das ferramentas JBoss.
É uma iniciativa muito importante para nós Brasileiros para que possamos contribuir cada vez mais a ferramentas Open Source.

Durante a primeira apresentação, Leandro Lima - um dos embaixadores da comunidade - mostrou os principais objetivos da comunidade e esclareceu dúvidas sobre o que é e o que não é a comunidade.

A noite também contou com a participação especial de Chris Morgan que é Senior Marketing Manager da plataforma JON, apresentando as ferramentas JON, Jopr e Embedded Jopr e suas diferenças.

Foi uma noite importante para a comunidade JBoss e principalmente para todos que puderam participar.

Veja as fotos em:
http://www.flickr.com/photos/alessandrolazarotti/3217148963/in/set-72157612860854836/

Notícias sobre o lançamento:

http://jbossbrasil.ning.com/forum/topics/lancamento-do-jbossbrasilorg

http://edgarsilva.com.br/2009/01/15/lancamento-dia-21-da-comunidade-jbossbrasil-em-sao-paulo/

http://www.leandrolima.eti.br/

http://br-linux.org/2009/red-hat-patrocina-lancamento-da-comunidade-jbossbrasilorg/

quarta-feira, 21 de janeiro de 2009

Breadcrumbs no JBoss Portal

Estive recentemente em um cliente que precisava adicionar Breadcrumbs em seu site criado em JBoss Portal para facilitar a navegação do usuário. Apenas para esclarecer um pouco antes de começar a parte técnica, Breadcrumb é um esquema de navegação o qual tem por objetivo mostrar o rastro, ou o caminho, percorrido em uma árvore de navegação de um site até chegar em sua página atual.

Imaginem que para entrar na página de seguros de um carro o caminho seja:

Home > Produtos > Seguros > Automóvel > Valor de Veículos

O caminho acima é um Breadcrumb!!! Na verdade, estamos apenas colocando um nome a uma coisa que todo mundo utiliza em sites e é vastamente conhecido. As pessoas que dedicam seu tempo em arquitetura de informação não teriam problemas com o termo "Breadcrumbs", porém, para nós meros mortais desenvolvedores de software especialmente em Java (JEE) acaba sendo um termo novo.

Recomendo fortemente a leitura de alguns artigos no site do Guilhermo Reis http://www.guilhermo.com para mais informações sobre arquitetura de informações.

Após esta breve introdução, iremos a focar na criação de Breadcrumbs para o JBoss Portal, o qual infelizmente ainda não conta com uma solução "bate-e-pronto".

Basicamente toda e qualquer página no seu portal é uma instância de PortalNode. Você pode verificar como exemplo a página
jboss-portal.sar/portal-core.war/WEB-INF/jsp/header/tabs.jsp
que é responsável em criar o menu superior original do JBoss Portal.

Veja abaixo um trecho de código retirado da tabs.jsp:

PortalNode root = (PortalNode)request.getAttribute("org.jboss.portal.api.PORTAL_NODE");
PortalNode portal = root;
PortalNode mainPage = portal;

while (portal.getType() != PortalNode.TYPE_PORTAL)
{
mainPage = portal;
portal = portal.getParent();
}

O código acima obtém o PORTAL_NODE (página) corrente e faz um loop obtendo seu "nó pai" ou "parent" até chegar em um PortalNode do tipo Portal.

Um PortalNode pode ser qualquer instância de Página, Portal, Contexto e Janela. A diferenciação de cada um deles é realizada com as constantes PortalNode.TYPE_PAGE, PortalNode.TYPE_CONTEXT, PortalNode.TYPE_PORTAL e PortalNode.TYPE_WINDOW.

Um dos grandes problemas que senti ao criar os Breadcrumbs foi a obtenção da página corrente em um .jsp ou em um Portlet. Infelizmente, não descobri uma forma fácil de obter isso diretamente no .jsp.

Uma das sacadas para inclusão do breadcrumb seria reaproveitar a tag p:region adicionando um nome para a região e um regionID, assim como é realizado atualmente para o dashboardnav e o navigation sendo incluída diretamente em seu layout, e utilizar Aspectos para inclui-lo dinamicamente em todas as ocorrências de p:region breadcrumb, por exemplo.

Outra forma de criar breadcrumb é desenvolver um Portlet e adiciona-lo manualmente nas páginas que deseja inclui-lo. Neste primeiro post irei mostrar apenas como criar um Portlet para o breadcrumb.

Abordagem com Portlet

O código fonte abaixo mostra o Portlet BreadCrumbsPortlet o qual herda de JBossPortlet, disponibilizando o request e o response no formato de JBossRenderRequest e JBossRenderResponse, o que irá facilitar a obtençao de PortalNodes.

public class BreadCrumbsPortlet extends JBossPortlet {

private static final String VIEW = "/WEB-INF/jsp/breadcrumb/view.jsp";

@Override
public void processAction(JBossActionRequest request, JBossActionResponse response) throws PortletException, IOException {

response.setPortletMode(PortletMode.VIEW);
return;
}

@Override
protected void doView(JBossRenderRequest request, JBossRenderResponse response) throws PortletException, IOException, UnavailableException {

response.setContentType("text/html");

//portlet utilizado
PortalNode node = request.getPortalNode();

List l = new ArrayList();

//portal node
while (node.getType() != PortalNode.TYPE_PORTAL) {
if (node.getType() == PortalNode.TYPE_PAGE) {
l.add(node);
}

node = node.getParent();
}

Object[] array = l.toArray();
List nodes = new ArrayList();
for (int i = array.length - 1; i >= 0 ; i--) {
nodes.add((PortalNode)array[i]);
}

request.setAttribute("org.jboss.portal.api.PORTAL_RUNTIME_CONTEXT", Navigation.getPortalRuntimeContext());
request.setAttribute("list", nodes);

PortletRequestDispatcher prd = getPortletContext().getRequestDispatcher(VIEW);
prd.include(request, response);
}
}

O método doView basicamente obtém o PortalNode corrente e vai adicionando cada um dos seus "parents" em uma lista, até chegar em um PortalNode que seja TYPE_PORTAL. Em seguida faz uma nova iteração ordenando na forma inversa e adicionando em uma lista de PortalNodes que será enviada para a tela.

O view.jsp irá apenas exbir os PortalNodes que já estão ordenados na tela adicionando links.

<%@ taglib uri="http://java.sun.com/portlet" prefix="portlet" %>

<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.Locale" %>
<%@ page import="java.util.Iterator" %>
<%@ page import="org.jboss.portal.api.node.PortalNode" %>
<%@ page import="org.jboss.portal.api.PortalRuntimeContext" %>

<portlet:defineObjects/>

<% String contextPath = renderResponse.encodeURL(renderRequest.getContextPath()); %>

<%

PortalRuntimeContext context = (PortalRuntimeContext)request.getAttribute("org.jboss.portal.api.PORTAL_RUNTIME_CONTEXT");

// Get a locale
Locale locale = request.getLocale();
if (locale == null)
{
locale = Locale.getDefault();
}

List l = (List)request.getAttribute("list");
Iterator iterator = l.iterator();
while (iterator.hasNext())
{
PortalNode node = (PortalNode)iterator.next();
%>
<a href="<%= node.createURL(context) %>"><%= node.getDisplayName(locale) %></a> <% if (iterator.hasNext()) { %> / <% } %>
<%
}
%>


Para fazer com que este Portlet funcione no JBoss Portal 2.7 ou superior, é necessário criar um filtro, conforme o link http://docs.jboss.org/jbportal/v2.7.0.B1/referenceGuide/html/changelog.html. Um exemplo do portlet.xml está abaixo:

<portlet-app
xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"
version="2.0">


<filter>
<filter-name>JBossPortletFilter</filter-name>
<filter-class>org.jboss.portlet.filter.JBossPortletFilter</filter-class>
<lifecycle>ACTION_PHASE</lifecycle>
<lifecycle>RENDER_PHASE</lifecycle>
</filter>

<filter-mapping>
<filter-name>JBossPortletFilter</filter-name>
<portlet-name>BreadCrumbsPortlet</portlet-name>
</filter-mapping>

<portlet>
<description>Simple portlet to create breadcrumbs</description>
<portlet-name>BreadCrumbsPortlet</portlet-name>
<portlet-class>com.jboss.breadcrumb.BreadCrumbsPortlet</portlet-class>
<supports>
<mime-type>text/html</mime-type>
<portlet-mode>VIEW</portlet-mode>
</supports>
<supported-locale>en</supported-locale>
<supported-locale>pt_BR</supported-locale>
<resource-bundle>BreadCrumbsResource</resource-bundle>
<portlet-info>
<title>BreadCrumb</title>
<keywords>breadcrumb, breadcrumbs</keywords>
</portlet-info>
</portlet>
</portlet-app>


Feito isto, faça o deploy do Portlet em seu JBoss Portal e adicione-o nas páginas que deseja.