Compartilhando recursos estáticos entre webapps JEE6 através de um JAR

Na API Servlet 3.0 é possível colocar recursos estáticos dentro de um JAR para serem acessados pelos webapps que dependem desse JAR. Para tanto, basta empacotar os recursos desejados na pasta META-INF/resources do JAR. Os arquivos aí colocados estarão disponíveis para os WAR como se estivessem na raíz do webapp. Ex:

O arquivo:

MeuWebApp.war/WEB-INF/lib/WebCommons.jar/META-INF/resources/public/img/logo.png

Estará disponível no servidor como:

http://localhost:8080/MeuWebApp/public/img/logo.png

No caso de um projeto Maven, essa pasta pode ser contemplada no código fonte em [PASTA DO PROJETO]/src/main/resources/META-INF/resources

Isso é bastante útil para compartilhar html, xhtml, jsp, css, js, imagens, arquivos de properties, dentre outros, entre diferentes WAR. Para obter o mesmo resultado nas versões anteriores da API era necessário bolar algum mecanismo que acessasse recursos programaticamente via ServletContext, ou mesmo utilizando bibliotecas de terceiros específicas para esse propósito, como é o caso da Weblets.

No caso de se tratar de um projeto JSF, pode ser desejado compartilhar entre os WAR não somente recursos estáticos como também ManagedBeans, Converters e outros beans gerenciados pelo framework. Para isso, crie no JAR também o arquivo META-INF/faces-config.xml.

Abandonando o antigo formulário j_security_check através de JSF 2 e Servlet 3

Muitas são as aplicações web em Java que utilizam o Serviço de Autenticação e Autorização do Java (JAAS) para fazer a segurança. E um elemento famoso no JAAS é o JSP/HTML de login que possui um formulário mágico que faz um POST para a URL “j_security_check“, enviando o password e a senha através dos inputs de nome “j_password” e “j_username” respectivamente. Muitos torcem o nariz para esse tipo de coisa, já que a plataforma está de certa forma engessando a tela de login.

As vantagens de querer fazer um formulário de login JSF 2 são muitas! Primeiramente você pode querer herdar o layout de um template via Facelets. Você pode também querer utilizar componentes ricos de bibliotecas como PrimeFaces ao invés de simples inputs HTML padrão. O JSF também dá um bom suporte para validação de campos e mensagens de erro/sucesso. Mas dadas as restrições impostas pelo j_security_check e seus amigos, como unir os dois mundos então?

Com a chegada do JEE 6 e a nova Servlet API 3.0, foi previsto um método bem óbvio na interface HttpServletRequest chamado request.login(String username, String password). O resto você provavelmente já imagina: pode-se acionar o JAAS programaticamente dentro de um ManagedBean do JSF por exemplo, unindo o melhor dos dois mundos.

A idéia então é fazer um XHTML de login e fazer um ManagedBean que cuide do comportamento dessa tela. Veja como fica a configuração de segurança do web.xml:

<?xml version="1.0"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee" 
        xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
           http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
	version="3.0">

	...
	
	<security-constraint>
		<web-resource-collection>
			<web-resource-name>private</web-resource-name>
			<description>private resources</description>
			<url-pattern>/private/*</url-pattern>
		</web-resource-collection>
		<auth-constraint>
			<role-name>*</role-name>
		</auth-constraint>
	</security-constraint>
	
	<login-config>
		<auth-method>FORM</auth-method>	
		<form-login-config>
			<form-login-page>/public/pages/security/login.jsf</form-login-page>
			<form-error-page>/public/pages/security/login.jsf</form-error-page>
		</form-login-config>
	</login-config>
	
	<security-role>
		<role-name>*</role-name>
	</security-role>

</web-app>

Perceba que no web.xml a página de login é tanto o formulário quanto a página de erro, ou seja, em caso de login inválido a aplicação retornará para a página de login, permitindo continuar interagindo com o formulário para exibir mensagens por exemplo. A página /public/pages/security/login.xhtml pode ser algo assim (observe que ela herda um template via Facelets):

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
	xmlns:p="http://primefaces.org/ui"
	xmlns:h="http://java.sun.com/jsf/html"
	xmlns:ui="http://java.sun.com/jsf/facelets"
	template="/WEB-INF/template/main.xhtml">
	<ui:define name="body">
		<p:messages id="messages" globalOnly="true" />
		<h:form id="form">			
			<h:panelGrid columns="3">
				<p:outputLabel value="Usuário" for="inputUsuario" />
				<p:inputText id="inputUsuario" required="true"
					value="#{loginMB.usuario}" />
				<p:message for="inputUsuario" />

				<p:outputLabel value="Senha" for="inputSenha" />
				<p:password id="inputSenha" required="true"
					value="#{loginMB.senha}" />
				<p:message for="inputSenha" />
			</h:panelGrid>

			<p:commandButton id="btEntrar" value="Entrar" ajax="false"
				action="#{loginMB.onClickLogar()}"
				icon="ui-icon-check" />
		</h:form>
	</ui:define>
</ui:composition>

E um exemplo de ManagedBean para tal página pode ser (os imports estão omitidos):

...

@ManagedBean
@ViewScoped
public class LoginMB {

    private static final String PAGINA_INDEX = "/private/pages/index.xhtml";

    private String usuario;
    private String senha;

    public String onClickLogar() {
        try {
	    HttpServletRequest request = (HttpServletRequest) FacesContext.
                getCurrentInstance().getExternalContext().getRequest();
            request.login(this.usuario, this.senha);
            return PAGINA_INDEX;
        } catch (ServletException e) {
	    //se houver erro no Login Module vai cair aqui... 
            //Você pode fazer log por exemplo!
        } finally {
            //tratar aqui mensagens de segurança que possam ter vindo 
            //do Login Module exibindo-as na forma de FacesMessage
	}

        return null;
    }

    public String getUsuario() {
        return usuario;
    }

    public void setUsuario(String usuario) {
        this.usuario = usuario;
    }

    public String getSenha() {
        return senha;
    }

    public void setSenha(String senha) {
        this.senha = senha;
    }
}

Não se esqueça de que para esse recurso de login programático estar disponível, sua aplicação deve adicionar a dependência provida a API do Servlet 3.0 no pom.xml:

<dependency>
	<groupId>javax</groupId>
	<artifactId>javaee-web-api</artifactId>
	<version>6.0</version>
	<scope>provided</scope>
</dependency>            

Vale ressaltar que o exemplo do post cobre apenas o essencial. Para atingir a excelência num esquema de login JAAS é preciso cuidar de vários outros aspectos. Um deles é definir um bom mecanismo de troca de mensagens do Login Module para com a Aplicação. Uma opção é adicionar a mensagem como atributo no Request, podendo capturá-la no ManagedBean e tranformá-la em uma FacesMessage. Outra questão que deve ser observada é o comportamento da aplicação quando a página de login é acessada por um usuário já logado, podendo redirecioná-lo ou retirá-lo da sessão, o que pode ser feito via JSF adicionando no seu XHTML de login um event listener na fase de Pre-Render View e implementando um listener correspondente:

...
   <f:event listener="#{loginMB.verificaSeUsuarioJaLogado}" type="preRenderView" />
...

Tenho certeza que partindo dessa solução base você poderá buscar várias sofisticações, fugindo do tradicional JSP de login para o JAAS.

Desabilitando um producer de terceiros no CDI

Entrei um problema no Jboss 7 AS / 6 EAP onde o WELD reclamou ainda no deploy da aplicão que existiam ambiguidade em uma injeção, devido a presença de dois produtores capazes de instanciar a mesma classe (no meu caso o FacesContext do JSF). Veja abaixo o erro:

org.jboss.weld.exceptions.DeploymentException: WELD-001409 Ambiguous dependencies for type 
     [FacesContext] with qualifiers [@Default] at injection point...

Para resolver precisei desativar um dos produtores. Isso foi feito criando uma Extension que veta o funcionamento de um produtor, no caso abaixo, do produtor org.apache.myfaces.extensions.cdi.jsf.impl.util.FacesInformationProducer do Apache MyFaces. Veja abaixo:

import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.ProcessAnnotatedType;

import org.apache.myfaces.extensions.cdi.jsf.impl.util.FacesInformationProducer;

public class FacesProducerSolverExtension implements Extension {

	public void observe(@Observes ProcessAnnotatedType p){
		if(p.getAnnotatedType().getJavaClass()
                    .equals(FacesInformationProducer.class)){
			p.veto();			
		}
	}
}

Para que esse extension funcione, ele deve ser registrado no arquivo de texto WEB-INF/classes/META-INF/services/javax.enterprise.inject.spi.Extension no seu WAR que ficará com o seguinte conteúdo:

br.gov.demoiselle.destdemoiselle.cgpolact.cdi.extension.FacesProducerSolverExtension

Resolvendo falta de classes para SSL do JDK no Jboss 7 AS / 6 EAP

No Jboss AS 7 / EAP 6 enfrentei uma situação onde quando a aplicação tentava estabelecer uma conexão segura via HTTPS (no caso era um webservice), acontecia o seguinte erro:

java.lang.NoClassDefFoundError: com/sun/net/ssl/internal/ssl/Provider

Pesquisando, percebí que essa classe fazia parte da própria JRE, mas no entanto, o Jboss não estava dando visibilidade dela para o ClassLoader da aplicação.

Para resolver, basta adicionar os seguintes path no arquivo XML do módulo da JDK localizado em JBOSS_HOME/modules/sun/jdk/main/module.xml:

...   
     <path name="com/sun/net/ssl/internal"/>
     <path name="com/sun/net/ssl/internal/ssl"/>
... 

Resolvendo problemas com caracteres especiais UTF-8 no JSF 2

As aplicações JSF 2 que crio e faço deploy no Jboss 7 AS / 6 EAP sempre apresentam problemas de encoding no envio de campos de formulários. Forçar o encoding do XHTML, do form, e outros truques parece não resolver pois alguém no meio do caminho entrega os caracteres especiais já estragados para o servidor.

Pesquisando, descobri que se trata de uma configuração do Servidor de Aplicações, no meu caso o Jboss. Uma maneira mais garantida de resolver o problema, sem apelar para configurações do próprio servidor, é garantindo que todo request que chega para a aplição está com o encoding correto, no meu caso UTF-8. Para tanto, um bom e velho filtro resolve, como segue:

...
@WebFilter("*.jsf")
public class CharacterEncodingFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
      throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        chain.doFilter(request, response);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void destroy() {
    }
}