Desenvolvimento

29 out, 2012

Apresentando Spring Roo – Parte 02: Desenvolvendo um aplicativo com Spring Roo

Publicidade

No artigo anterior, que iniciou esta série, desenvolvemos um pequeno aplicativo de conferência corporativo usando o Spring Roo. Aqui, vamos estender esse aplicativo da web CRUD simples para se tornar um aplicativo corporativo completo usando o Spring Roo. Antes de começarmos, confirme se o Spring Roo está instalado e se o SpringSource Tool Suite foi transferido por download (consulte a Parte 1 para obter mais informações).

Vamos começar

Para estender nosso aplicativo de conferência, precisamos recriar o aplicativo da Parte 1. Podemos seguir as instruções anteriores ou usar o comando script do Roo. script executa todos os comandos especificados em um arquivo de recursos. Se você acompanhou a Parte 1, notou que o Roo criou um arquivo chamado log.roo, que contém todos os comandos disparados no shell do Roo. Executaremos esse arquivo log.roo e recriaremos o aplicativo.

  1. Esse arquivo é incluído no código de amostra. É possível renomeá-lo para conference.roo.
  2. Crie um novo diretório chamado conference e copie conference.roo para ele.
  3. Abra o shell de linha de comando do seu sistema operacional.
  4. Acesse o diretório conference que acabou de criar.
  5. Dispare o comando script --file conference.roo .

script recriará o aplicativo em alguns segundos se os JARs necessários estiverem no seu repositório do Maven. Caso contrário, levará um pouco mais de tempo, visto que precisará fazer o download de todos os JARs. O comando script é útil no sentido de que pode ser utilizado como modelo para criar projetos gerenciados por Spring.

Antes de prosseguirmos, importe o projeto de Maven para STS. O STS vem pré-empacotado com o plug-in do Eclipse de Maven. Deve-se importar o projeto selecionando File > Import > Maven > Existing Maven Projects, e selecionando depois o diretório do projeto. Estamos importando o projeto para o STS, visto que vamos escrever código customizado mais tarde.

O aplicativo da web que criamos até agora funciona, e podemos testá-lo manualmente criando, lendo, atualizando e excluindo as entidades Speaker e Talk. Mas não seria ótimo poder automatizar esse processo?

Teste da web automatizado

Este é o próximo recurso do Spring Roo que vamos analisar: o suporte a testes Selenium. O Selenium é um conjunto robusto de ferramentas com suporte a desenvolvimento rápido de automação de teste para aplicativos baseados na web. Para incluir suporte aos testes Selenium no seu aplicativo, execute os comandos mostrados abaixo:

selenium test --controller ~.web.SpeakerController
selenium test --controller ~.web.TalkController

O comando selenium test criará um teste Selenium para os controladores Speaker e Talk. Esse comando tem um atributo obrigatório chamado controller para especificar o nome do controlador a fim de criar o teste Selenium. Esse comando também tem dois atributos opcionais chamados name e serverUrl para especificar o nome do teste Selenium e do servidor onde o aplicativo da Web está disponível. O Spring Roo também incluirá o plug-in Selenium Maven ao disparar o comando selenium test .

Acima, criamos etapas de teste Selenium para nosso controlador, mas, antes de executá-las, precisamos corrigir um pequeno defeito no conjunto de testes Selenium criado pelo Spring Roo. Incluímos uma restrição na nossa entidade Speaker de que a idade deve estar entre 25 e 60, mas o conjunto de testes não leva em conta essa restrição. Ele usou um valor de idade como 1, de modo que o teste falhará. Precisamos modificar um arquivo chamado test-speaker.xhtml e atualizar a seção mostrada na Listagem 1.

Listagem 1. Modificação de test-speaker.xhtml

		<tr>
			<td>type</td>
			<td>_age_id</td>
			<td>1</td>
		</tr>

to

		<tr>
			<td>type</td>
			<td>_age_id</td>
			<td>26</td>
		</tr>

Esse defeito será corrigido em um release futuro do Spring Roo. Para executar as etapas de teste Selenium, precisamos iniciar o servidor Tomcat. Pode-se iniciá-lo usando o comando de Maven mvn tomcat:run. Por padrão, todos os aplicativos da web criados usando o Roo têm plug-ins Maven para servidores da web Tomcat e Jetty. Para executar selenium test, execute o comando de Maven mvn selenium:selenese. Isso ativará o navegador Firefox para executar as etapas de teste Selenium. Durante a execução dos testes, deve aparecer uma imagem similar à Figura 1.

Figura 1. Testes Selenium

 

No momento, qualquer um pode acessar nosso aplicativo e criar, atualizar ou excluir em Speaker e Talk. Em um aplicativo em tempo real, é aplicada segurança quanto a quem pode executar qual operação.

Tornar seguro o aplicativo da web

O Roo usa Spring Security para incluir segurança no seu aplicativo em uma linha. Spring Security é uma estrutura de autenticação e controle de acesso poderosa e altamente customizável. Na prática, trata-se do padrão para tornar seguros os aplicativos baseados em Spring.

Incluindo Spring Security

Para incluir o Spring Security, digite o seguinte comando: security setup. Esse comando incluirá todos os JARs de Spring Security necessários e configurará a segurança básica para o seu aplicativo. Esse comando também cria outros arquivos, mas o mais importante é applicationContext-security.xml, que contém todas as definições de bean relacionadas à segurança. O contexto applicationContext-security.xml se parece com o conteúdo da Listagem 2. Substituí a senha com hash por pontos para torná-la mais legível.

Listagem 2. Contexto de applicationContext-security.xml

<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans \
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                        http://www.springframework.org/schema/security \
http://www.springframework.org/schema/security/spring-security-3.0.xsd">

	<!-- HTTP security configurations -->
    <http auto-config="true" use-expressions="true">
    	<form-login login-processing-url="/resources/j_spring_security_check" \
login-page="/login" authentication-failure-url="/login?login_error=t"/>
        <logout logout-url="/resources/j_spring_security_logout"/>

        <!-- Configure these elements to secure URIs in your application -->
        <intercept-url pattern="/choices/**" access="hasRole('ROLE_ADMIN')"/>
        <intercept-url pattern="/member/**" access="isAuthenticated()" />
        <intercept-url pattern="/resources/**" access="permitAll" />
        <intercept-url pattern="/**" access="permitAll" />
    </http>

	<!-- Configure Authentication mechanism -->
    <authentication-manager alias="authenticationManager">
    	<!-- SHA-256 values can be produced using \
'echo -n your_desired_password | sha256sum' \
(using normal *nix environments) -->
    	<authentication-provider>
	    	<password-encoder hash="sha-256"/>
	        <user-service>
	            <user name="admin" password="..." authorities="ROLE_ADMIN"/>
		        <user name="user" password="..." authorities="ROLE_USER"/>
		    </user-service>
    	</authentication-provider>
	</authentication-manager>

</beans:beans>

A segurança configurada pelo Roo é genérica e não faz referência ao seu aplicativo. Tenha em mente de que o Roo ajuda a configurar um aplicativo para iniciação rápida, mas é responsabilidade do desenvolvedor customizar o produto final. Nesse caso, o Roo simplesmente forneceu um modelo para Spring Security, e é sua responsabilidade customizá-lo de acordo com as suas necessidades.

Customizando o Spring Security

No nosso aplicativo, qualquer um pode criar um Speaker, mas apenas um Speaker pode criar um Talk. Precisamos modificar applicationContext-security.xml como mostrado abaixo. A Listagem 3 mostra apenas a parte do XML que precisa de modificação.

Listagem 3. Modificando applicationContext-security.xml

<http auto-config="true" use-expressions="true">
    	<form-login login-processing-url="/resources/j_spring_security_check" \
login-page="/login" authentication-failure-url="/login?login_error=t"/>
        <logout logout-url="/resources/j_spring_security_logout"/>

        <!-- Configure these elements to secure URIs in your application -->
        <intercept-url pattern="/talks/**" access="hasRole('ROLE_USER')"/>
        <intercept-url pattern="/speakers/**" access="permitAll" />
        <intercept-url pattern="/resources/**" access="permitAll" />
        <intercept-url pattern="/**" access="permitAll" />
</http>

Atualizei o intercept-url para que apenas usuários com função de usuário possam criar um Talk, e todos os usuários têm permissão de se registrar como Speaker.

O Spring Security gerado por Roo e mostrado acima usa um provedor de autenticação na memória configurado na tag <user-service> . Visto que nosso aplicativo gerencia entidades Speaker, devemos desenvolver um provedor de autenticação customizado que use dados do Speaker. Para autenticação, usaremos o e-mail de Speaker como nome de usuário e incluiremos um campo de senha na entidade Speaker, que usaremos como senha de autenticação.

Novamente, o shell do Roo foi usado para incluir o campo de senha na entidade Speaker:

field string --class ~.domain.Speaker --fieldName password --notNull --sizeMin 6 –sizeMax
10

Uma restrição de que a senha não deve ser nula e que deve ter entre 6 e 10 caracteres também foi acrescentada.

Visto que estamos usando e-mail e senha como parâmetros de autenticação, gostaríamos de encontrar um Speaker com determinado e-mail e senha. O Spring Roo fornece um recurso para criar localizadores no seu aplicativo usando o comandofinder add :

finder add --finderName findSpeakersByEmailAndPasswordEquals --class ~.domain.Speaker

É possível localizar todos os localizadores de uma entidade usando o comando finder list . finder add escreve o código do localizador no arquivo Speaker_Roo_Finder.aj e escreve alguns arquivos relacionados à visualização. Isso permite procurar oradores a partir da interface gráfica com o usuário.

Escrevendo AuthenticationProvider customizado

Vamos escrever um provedor de autenticação customizado estendendo a classe chamada AbstractUserDetailsAuthenticationProvider, que funciona com nome de usuário/senha para autenticação. As classes que estendem o AbstractUserDetailsAuthenticationProvider precisam fornecer implementação para seus dois métodos abstratos: additionalAuthenticationChecks e retrieveUser. O provedor chama o método retrieveUser para autenticar o Speaker usando o e-mail e a senha inseridos. A consulta ao banco de dados pelo Speaker é feita usando o localizador criado acima. Se o Speaker for localizado, GrantedAuthority ROLE_USER é designado ao Speaker. O método finalmente retorna um objeto UserDetails preenchido se o login for bem-sucedido ou lança uma exceção BadCredentialsException com a mensagem adequada se não for (consulte a Listagem 4).

Listagem 4. Autenticação customizada 

package com.dw.roo.conference.security;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.EntityNotFoundException;
import javax.persistence.NonUniqueResultException;

import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.\
dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.StringUtils;

import com.dw.roo.conference.domain.Speaker;

public class ConferenceAuthenticationProvider extends \
AbstractUserDetailsAuthenticationProvider {

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, \
UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        // TODO Auto-generated method stub

    }

    @Override
    protected UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken \
authentication) throws AuthenticationException {
        String password = (String) authentication.getCredentials();
        if (!StringUtils.hasText(password)) {
            throw new BadCredentialsException("Please enter password");
        }
        List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
        try {
            Speaker speaker = Speaker.findSpeakersByEmailAndPasswordEquals(username, \
password).getSingleResult();
            authorities.add(new GrantedAuthorityImpl("ROLE_USER"));
        } catch (EmptyResultDataAccessException e) {
            throw new BadCredentialsException("Invalid username or password");
        } catch (EntityNotFoundException e) {
            throw new BadCredentialsException("Invalid user");
        } catch (NonUniqueResultException e) {
            throw new BadCredentialsException("Non-unique user, contact administrator");
        }
        return new User(username, password, true, // enabled
                true, // account not expired
                true, // credentials not expired
                true, // account not locked
                authorities);
    }
}

Em applicationContext-security.xml, precisamos definir o bean conferenceAuthenticationProvider e substituir o provedor de autenticação na memória gerado pelo Roo pelo nosso conferenceAuthenticationProvider , conforme a Listagem 5.

Listagem 5. conferenceAuthenticationProvider

<beans:bean name="conferenceAuthenticationProvider"
class="com.dw.roo.conference.security.ConferenceAuthenticationProvider">
</beans:bean>

<!-- Configure Authentication mechanism -->
<authentication-manager alias="authenticationManager">
	<authentication-provider ref="conferenceAuthenticationProvider"/>
</authentication-manager>

Agora, se inicializarmos o servidor e tentarmos criar um Talk, aparecerá uma tela de login na qula será preciso inserir o e-mail e a senha do Speaker criado. O provedor de autenticação customizado foi criado para mostrar o que o Roo faz e o que é necessário fazer sozinho, e não criar um provedor ideal.

Notificação por e-mail

No nosso aplicativo, Speakers deve receber um e-mail quando criar um Talk. Por isso, vamos incluir suporte a e-mail no nosso aplicativo. O Gmail será usado como nosso servidor SMTP para nos concentrar em enviar e-mail usando o Roo. A inclusão de suporte a e-mail em um aplicativo é feita com o uso do seguinte comando:

email sender setup --hostServer smtp.gmail.com --username \
<Your email address> --password <Your email password> --port 587 --protocol SMTP

O comando do remetente de e-mail instala um JavaMailSender de Spring no seu projeto. É possível alterar as propriedades relacionadas ao e-mail no arquivo email.properties.

Após um Talk ser criado, precisamos enviar o e-mail. Para isso, precisamos incluir um campo de e-mail em TalkController. Para incluir o campo de e-mail, digite:

field email template --class ~.web.TalkController

Isso incluirá um modelo MailSender e um método sendMessage em TalkController. Agora, precisamos acionar o métodosendMessage após um Talk ser persistido no banco de dados. Visto que todo o código de TalkController existe no arquivo TalkController_Roo_Controller.aj, a forma mais fácil de executar essa tarefa é criar um método encodeUrlPathSegment a partir do arquivo .aj na classe TalkController e incluir uma chamada para o método sendMessage após a linhatalk.persist() , como mostrado na Listagem 6.

Listagem 6. Criando um método encodeUrlPathSegment a partir do arquivo .aj em TalkController

public class TalkController {

	@Autowired
	private transient MailSender mailTemplate;

	public void sendMessage(String mailFrom, String subject, String mailTo,
			String message) {
		org.springframework.mail.SimpleMailMessage \
simpleMailMessage = new org.springframework.mail.SimpleMailMessage();
		simpleMailMessage.setFrom(mailFrom);
		simpleMailMessage.setSubject(subject);
		simpleMailMessage.setTo(mailTo);
		simpleMailMessage.setText(message);
		mailTemplate.send(simpleMailMessage);
	}

	@RequestMapping(method = RequestMethod.POST)
	public String create(@Valid Talk talk, BindingResult result, Model model,
			HttpServletRequest request) {
		if (result.hasErrors()) {
			model.addAttribute("talk", talk);
			return "talks/create";
		}
		talk.persist();
		sendMessage("spring.roo.playground@gmail.com", "Your talk is created",
				talk.getSpeaker().getEmail(), \
"Congrats your talk is created");
		return "redirect:/talks/"
				+ encodeUrlPathSegment(talk.getId().toString(), request);
	}

	private String encodeUrlPathSegment(String pathSegment,
			HttpServletRequest request) {
		String enc = request.getCharacterEncoding();
		if (enc == null) {
			enc = WebUtils.DEFAULT_CHARACTER_ENCODING;
		}
		try {
			pathSegment = UriUtils.encodePathSegment(pathSegment, enc);
		} catch (UnsupportedEncodingException uee) {
		}
		return pathSegment;
	}
}

De agora em diante, os oradores receberão um e-mail em suas contas especificadas após a criação de um Talk.

Suporte a internacionalização

Visto que estamos desenvolvendo um aplicativo web baseado na Internet, é importante oferecer suporte a idiomas diferentes para que usuários de diferentes locais possam utilizá-lo. O Spring Roo inclui suporte à internacionalização ao usar o comando web mvc install language , que instala um novo idioma no seu aplicativo. Por exemplo, os comandos para espanhol e italiano são:

web mvc install language --code es 
web mvc install language --code it

Atualmente, o Roo oferece suporte a seis idiomas, e é possível escrever um complemento para qualquer idioma à sua escolha. Agora, quando o aplicativo estiver em execução, duas bandeiras serão exibidas (da Itália e da Espanha), juntamente com a bandeira britânica. Se clicarmos em uma dessas bandeiras, o aplicativo da web será visualizado no idioma correspondente à bandeira.

Socializar o aplicativo web

Estamos na era da mídia social, e recursos sociais costumam ser incluídos nos aplicativos atuais. Faz sentido incluir vídeo de Talks. O Roo fornece suporte para inclusão de vídeos transferidos por upload para YouTube, Vimeo, Viddler e Google Video etc. Para incluir um vídeo, use o seguinte comando:

web mvc embed video --provider VIMEO --videoId 16069687

Se iniciar o servidor e ativar seu aplicativo em um navegador, será possível assistir ao vídeo integrado acima. De modo semelhante, também é possível incluir vídeos do YouTube ou Viddler.

O Roo também fornece a opção de integrar mensagens do Twitter, documentos, barras de notícias, mapas, fotos e conexões de vídeo ao seu aplicativo. Os vários comandos encontram-se na Listagem 7.

Listagem 7. Integrar comandos

web mvc embed document 
web mvc embed finances 
web mvc embed map
web mvc embed photos 
web mvc embed stream video 
web mvc embed twitter
web mvc embed video

Engenharia reversa de banco de dados

A engenharia reversa de banco de dados (DBRE) permite uma introspecção em um banco de dados existente e expô-lo como aplicativo. Para mostrar como funciona a DBRE, um aplicativo de feedback será criado a partir de um esquema de feedback existente. MySQL será usado como banco de dados.

Antes de ativar o Roo, precisamos criar um esquema em nossa instalação do MySQL. Execute o script SQL da Listagem 8 para criar um esquema de feedback em seu banco de dados MySQL.

Listagem 8. Script SQL para criar esquema de feedback

create database feedback_schema;
use feedback_schema;
CREATE TABLE feedback (
  id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
  TalkTitle VARCHAR(45) NOT NULL,
  SpeakerName VARCHAR(45) NOT NULL,
  Feedback VARCHAR(4000) NOT NULL,
  PRIMARY KEY (id)
)
ENGINE = InnoDB;

Esse é um esquema simples, com uma tabela e nenhum relacionamento, mas o Roo também consegue fazer engenharia reversa de um esquema complexo, com diversas tabelas e relacionamentos.

Depois de executar o script SQL acima e gerado seu esquema, crie um aplicativo de feedback:

  1. Crie um diretório chamado feedback.
  2. Na sua linha de comando do sistema operacional, acesse o diretório feedback.
  3. Abra o shell do Roo digitando o comando roo .
  4. Digite project --topLevelPackage com.dw.roo.feedback para criar um projeto de Maven.
  5. Nesse aplicativo, usarei MySQL como banco de dados. Para configurar a persistência do seu aplicativo, use:
    persistence setup --provider HIBERNATE 
    --database MYSQL --databaseName feedback_schema 
      --userName root --password password

    Considerando que criei o esquema com usuário raiz, uso root como meu nome de usuário. Digite o nome de usuário e a senha criados no seu esquema. Esse comando também incluirá todos os JARs necessários para persistência.

  6. Pode-se fazer a introspecção do esquema do banco de dados usando database introspect --schema feedback_schema. O comando database introspect mostra os metadados relacionados ao esquema do banco de dados. Esse comando lhe mostrará os metadados do seu esquema no console de shell do Roo. Também é possível exportar o xml de metadados para um arquivo usando --file attribute.
  7. Depois de ter feito a introspecção do seu esquema de banco de dados, poderá fazer a engenharia reversa desse esquema usando database reverse engineer --schema feedback_schema --package ~.domain. O comando database reverse engineer tem dois atributos obrigatórios, schema e package, para especificar o nome do esquema no qual se deseja realizar engenharia reversa e o pacote no qual o Roo gerará as origens. Isso criará todas as entidades no pacote com.dw.roo.feedback.domain.
  8. A próxima etapa é gerar os controladores do seu aplicativo. Isso pode ser feito disparando controller all --package ~.web.
  9. Antes de executar nosso aplicativo, precisamos fazer uma pequena mudança em uma propriedade de persistence.xml. A propriedade hibernate.ejb.naming_strategy está usando ImprovedNamingStrategy, que não funciona em bancos de dados MySQL e gerará uma exceção se for executado mvn clean install tomcat:run. Para fazê-lo funcionar, é preciso alterar hiberate.ejb.naming_strategy para DefaultNamingStrategy, conforme mostrado abaixo:
    <property name="hibernate.ejb.naming_strategy" 
    value="org.hibernate.cfg.DefaultNamingStrategy"/>
  10. Agora é possível executar o aplicativo de feedback usando o comando de Maven mvn clean install tomcat:run.

É possível fazer o download do código de origem para conferência e feedback (consulte Download).

Conclusão

Neste ponto, estendemos nosso aplicativo da web simples CRUD para se tornar um aplicativo corporativo completo. Demonstramos como é simples incluir recursos como teste Selenium, Spring Security, suporte à internacionalização e outros. Também mostrei como criar um aplicativo a partir de um banco de dados existente usando o recurso de engenharia reversa de banco de dados do Spring Roo. Ainda há muitos recursos, como JMS, Solr e suporte a JSON etc. que o Roo pode incluir facilmente no seu aplicativo.

No próximo artigo desta série, falarei sobre como podemos transferir o aplicativo de conferência para um Google App Engine.