Access Control List: Protegendo o acesso a seus objetos

O Spring Security é um dos módulos mais usados do Spring e sem dúvida cumpre o seu papel muito bem. Ele é bastante usado para restringirmos o acesso aos métodos e urls da nossa aplicação baseado nos perfis dos usuários do sistema. Uma situação mais complicada pode aparecer quando dois usuários possuem um perfil que permite o acesso a uma mesma url, por exemplo um endereço que exibe o detalhe de uma prova dele em um sistema de provas online. Segue abaixo o exemplo:

  /prova/resolucao/1

E se o outro usuário logado também tentar acessar o mesmo endereço de detalhe? Como você vai negar o acesso? Provavelmente acabaríamos escrevendo um código parecido com o que segue.

	Usuario usuarioLogado = ...;
	ResolucaoProva resolucao = resolucaoProvaDao.carrega(idResolucao);
	if(!resolucao.pertence(usuarioLogado)){
		throw new SemAutorizacaoException("espertinho...");
	}

Essa é uma solução perfeitamente válida. O ponto negativo é que você não vai reaproveitar para os outros sistemas. Além do mais, esse sistema de permissões pode ficar mais complexo com usuários podendo ver detalhes de outros usuários, mas sem possibilidade edição por exemplo. Tudo vai depender do tipo de aplicação que você estiver desenvolvendo.

Para amenizar essa complexidade, o Spring Security tem um módulo chamado de ACL(Access Control List). O objetivo dele é justamente generalizar essa implementação nos dando a possibilidade de aplicar regras de acesso para qualquer tipo de objeto! Essa semana eu e Rafael Rollo utilizamos ele em um projeto dentro da Caelum. O objetivo do post é justamente mostrar o código necessário para deixar tudo funcionando. Respire bastante, porque não é pouco código não.

Vamos começar pela parte mais interessante, pelo menos para mim, que é a de inserir as permissões de acesso para determinado objeto. Dê uma olhada no código abaixo:

	@Autowired
	private Permissoes permissoes;

	...

	@RequestMapping(value = "/finaliza", method = RequestMethod.POST)
	public String finaliza(HttpSession session,@AuthenticationPrincipal 
				Usuario usuarioLogado) {

		ResolucaoFinal resolucaoFinal = resolucaoEmAndamento
				.geraResolucaoFinal();
		resolucaoFinalDao.save(resolucaoFinal);

		//essa classe isola o código de adicionar permissões
		permissoes.adiciona(usuarioLogado,resolucaoFinal,BasePermission.READ);

		return "redirect:/resolucao/finalizada";
	}
	@Service
	public class Permissoes {
		
		@Autowired
		private MutableAclService mutableAclService;	

		public void adiciona(UserDetails user, Object objeto, 
                      Permission... permissoes) {
	        ObjectIdentity identity = new ObjectIdentityImpl(objeto);
	        MutableAcl acl = mutableAclService.createAcl(identity);
	        PrincipalSid principalSid = new PrincipalSid(user.getUsername());
	        for (Permission permissao : permissoes) {
	        	acl.insertAce(acl.getEntries().size(), permissao, principalSid, true);			
			}
	        mutableAclService.updateAcl(acl);	
		}

	}

Vamos focar na classe Permissoes, já que nela está acontecendo a parte que nos interessa. A classe MutableAclService é a nossa porta de entrada para criar, atualizar e ler as permissões de acesso a um objeto. Tanto que nesse pequeno trecho de código usamos dois métodos dela:

  1. createAcl
  2. updateAcl

Vamos analisá-los por partes, começando pelo createAcl. 

		public void adiciona(UserDetails user, Object objeto, 
                        Permission... permissoes) {
	        ObjectIdentity identity = new ObjectIdentityImpl(objeto);
	        MutableAcl acl = mutableAclService.createAcl(identity);
                ...

O createAcl espera como argumento um ObjectIdentity, que é justamente a representação do objeto que vai ter o acesso controlado. O retorno deste método é um objeto do tipo MutableAcl, a abstração para as permissões de um objetoComo você já deve ter notado, o construtor do ObjectIdentity recebe um Object como parâmetro, já que qualquer tipo de objeto pode ser protegido. Um ponto importante é que esse Object deve ter um método chamado getId, cujo retorno será usado como chave estrangeira nas tabelas que serão criadas(ainda vamos comentar sobre isso). Caso sua classe não possua um método chamado getId, você pode usar o outro construtor que recebe além do objeto, um id como argumento.

Para falarmos do updateAcl, precisamos primeiro dar uma olhada no método insertAcl da classe MutableAcl. Através dele é que vamos adicionando permissões para o nosso objeto.

	        MutableAcl acl = mutableAclService.createAcl(identity);
	        PrincipalSid principalSid = new PrincipalSid(user.getUsername());
	        for (Permission permissao : permissoes) {
	           acl.insertAce(acl.getEntries().size(), permissao, principalSid, true);			
		}

Para ser bem sincero, essa assinatura de método é um pouco estranha para mim :/. Vamos começar pelos argumentos que fazem mais sentido, o segundo e o terceiro. O segundo argumento é um objeto cuja classe implementa a interface Permission, que é a abstração de uma permissão dentro do ACL. Ele já tem uma implementação base que é tranquila para a gente usar.

	public class BasePermission extends AbstractPermission {
		public static final Permission READ = new BasePermission(1 << 0, 'R'); // 1
		public static final Permission WRITE = new BasePermission(1 << 1, 'W'); // 2
		public static final Permission CREATE = new BasePermission(1 << 2, 'C'); // 4
		public static final Permission DELETE = new BasePermission(1 << 3, 'D'); // 8
		public static final Permission ADMINISTRATION = new BasePermission(1 << 4, 'A'); // 16

		protected BasePermission(int mask) {
			super(mask);
		}

		protected BasePermission(int mask, char code) {
			super(mask, code);
		}
	}

Caso você esteja questionando o motivo da permissão ser representada por um int, bem vindo ao time :), neste link eles discutem sobre a decisão. O terceiro argumento do método é justamente o dono do objeto, que é representado pela classe PrincipalSid(Principal Security Identity).  Passamos como argumento no construtor dele um identificador desse “dono” que existe dentro do nosso sistema, no meu caso foi o email do usuário.

Pensando de maneira direta, os argumentos da permissão e do dono daquela permissão deveriam ser suficientes para definir a regra de acesso ao objeto, só que ainda temos outros dois. Vou tentar ser breve aqui, já que para mim eles não fazem muito sentido. A classe MutableAcl possui um outro método chamado updateAce, cuja assinatura segue abaixo:

    void updateAce(int aceIndex, Permission permission) throws NotFoundException;

Perceba que o primeiro argumento é um índice que é necessário para localizar a posição da permissão. Como para atualizar precisa do índice, para inserir você também precisar passar qual é a posição que você deseja adicionar a nova permissão. Essa é a razão do primeiro argumento do método insertAce ser um inteiro. O último argumento é um boolean para indicar se você realmente quer dar aquela permissão de acesso ao objeto. O que eu não entendo é: se você não quer dar permissão de acesso, não é melhor simplesmente não adicionar? Sei lá, devo estar me passando em alguma coisa aqui.

Com as permissões inseridas no objeto do tipo MutableAcl, podemos invocar o updateAcl da classe MutableAclService, para que tudo seja refletido no banco de dados.

Uma vez que as permissões estão associadas, você deve estar se perguntando como vamos proteger o acesso ao método. Dê uma olhada no código abaixo.

	@RequestMapping(value = "/prova/resolucao/{id}", method = RequestMethod.POST)
	@PreAuthorize("hasPermission(#id,'br.com.caelum.provas.models.ResolucaoFinal','read')")
	public String visualiza(@PathVariable("id") Integer id) {
		ResolucaoFinal resolucao = resolucaoDao.carrega(id);
		...
	}

Aqui fizemos uso das expressões suportadas pelo Spring. Por sinal esse é um tema que merece um post aqui no blog :). Temos acesso a função hasPermission que recebe três argumentos.

  1. O id objeto que está tentando ser acessado. Repare que usamos o #” para indicar que ele deve pegar esse valor do argumento de mesmo nome.
  2. A classe do objeto que ele precisa carregar.
  3. O nome da permissão que o usuário logado precisa ter. Esse nome precisa ser o mesmo da constante usada na hora de adicionar a permissão. Por default o ACL vai fazer o carregamento da permissão baseado nessa condição.

O Spring Security ACL vai verificar se o usuário logado tem acesso especificamente a este objeto. Essa é uma solução super genérica e que você pode aproveitar para qualquer projeto seu!

Para fechar, precisamos realizar as configurações necessárias para que tudo funcione. Essa parte toda eu vou deixar isolada em uma classe específica.

	@Configuration
	public class ConfiguracaoACL {

		@Autowired
		private DataSource dataSource;

		@Bean
		public AclAuthorizationStrategy aclAuthorization() {
			AclAuthorizationStrategyImpl aclAuthorizationStrategy = new AclAuthorizationStrategyImpl(
					new SimpleGrantedAuthority("ROLE_ADMIN"));				
			return aclAuthorizationStrategy;
		}

		@Bean
		public PermissionGrantingStrategy permissionGrantingStrategy() {
			PermissionGrantingStrategy grantingStrategy = new DefaultPermissionGrantingStrategy(
					new ConsoleAuditLogger());
			return grantingStrategy;
		}

		@Bean	
		public Ehcache ehCache() {
			CacheManager cacheManager = CacheManager.create();
			if (!cacheManager.cacheExists("aclCache")) {
				cacheManager.addCache("aclCache");
			}

			Ehcache ehCache = cacheManager.getEhcache("aclCache");
			return ehCache;
		}
			

		@Bean
		public AclCache aclCache(Ehcache ehCache, PermissionGrantingStrategy grantingStrategy,
				AclAuthorizationStrategy aclAuthorizationStrategy) {
			AclCache aclCache = new EhCacheBasedAclCache(ehCache, grantingStrategy,
					aclAuthorizationStrategy);

			return aclCache;
		}

		@Bean
		public BasicLookupStrategy basicLookupStrategy(AclCache aclCache,
				AclAuthorizationStrategy aclAuthorizationStrategy,
				PermissionGrantingStrategy grantingStrategy) {

			return new BasicLookupStrategy(dataSource, aclCache, aclAuthorizationStrategy,
					grantingStrategy);
		}

		@Bean
		public MutableAclService mutableAclService(BasicLookupStrategy lookupStrategy, AclCache aclCache) {
			JdbcMutableAclService service = new JdbcMutableAclService(dataSource, lookupStrategy,
					aclCache);
			service.setClassIdentityQuery("select last_insert_id()");
			service.setSidIdentityQuery("select last_insert_id()");
			return service;
		}

		@Bean
		public MethodSecurityExpressionHandler expressionHandler(PermissionEvaluator permissionEvaluator) {
			DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
			handler.setPermissionEvaluator(permissionEvaluator);
			return handler;
		}

		@Bean
		public PermissionEvaluator permissionEvaluator(MutableAclService aclService) {
			AclPermissionEvaluator aclPermissionEvaluator = new AclPermissionEvaluator(aclService);
			return aclPermissionEvaluator;
		}

	}

Aqui você precisa medir seus esforços. Não se pode negar que cada um desses métodos produz um objeto que é necessário para o funcionamento, mas não acho saudável gastar vários parágrafos explicando um por um. Vou deixar uma lista apenas com uma breve ideia :).

  1. LookupStrategy: dependência do JdbcMutableAclService para fazer buscas no banco pelas permissões.
  2. AclCache: Camada de cache para não ficar indo no banco o tempo todo.
  3. AclAuthorizationStrategy: Verifica se certa role pode alterar permissões de um objeto.
  4. PermissionGrantingStrategy: Verifica se certo usuário pode realizar uma operação sobre o objeto.
  5. PermissionEvalutaor: Necessária para suportar o hasPermission nos métodos.

Como é de praxe, aconselho que você olhe o Javadoc de todas elas, para saber exatamente do papel de cada uma. Aproveitando, já vou fazer uma atualização no SetupMyProject para suportar a configuração do ACL :). Para completar é necessário criar as tabelas necessárias para que o módulo do ACL funcione e, além disso, modificar sua classe de configuração do Spring Security e adicionar a annotation @EnableGlobalMethodSecurity(prePostEnabled=true). Essa annotation habilita a checagem da annotation @PreAuthorize :).

Para você não esquecer, também deixo as dependências que precisam ser adicionadas no pom.xml.

		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-acl</artifactId>
		</dependency>
		<!-- cache -->

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-cache</artifactId>
		</dependency>

		<dependency>
			<groupId>net.sf.ehcache</groupId>
			<artifactId>ehcache</artifactId>
		</dependency>

Pronto! O post foi um pouco longo, mas também o uso e configuração dele não é lá uma das coisas mais fáceis. E esse foi só o primeiro deste ano, se preparem para mais. Também não deixem de mandar sugestões para temas, por aqui ou me referenciando no twitter.

Advertisements

Configurações automáticas do Spring Boot

Uma das mágicas do Spring Boot é que colocamos uma nova dependência no nosso projeto e magicamente tudo ou quase tudo já está configurado. A pergunta que fica é: como essa configuração é feita? Aí entra as classes denominadas de AutoConfigurations do Spring Boot.

Vamos dar uma olhada na classe abaixo:

	@Configuration
	@ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class,
			EnableTransactionManagement.class, EntityManager.class })
	@Conditional(HibernateEntityManagerCondition.class)
	@AutoConfigureAfter({ DataSourceAutoConfiguration.class })
	public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration {

		private static final Log logger = LogFactory
				.getLog(HibernateJpaAutoConfiguration.class);

		private static final String JTA_PLATFORM = "hibernate.transaction.jta.platform";

		/**
		 * {@code NoJtaPlatform} implementations for various Hibernate versions.
		 */
		private static final String NO_JTA_PLATFORM_CLASSES[] = {
				"org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform",
				"org.hibernate.service.jta.platform.internal.NoJtaPlatform" };

		/**
		 * {@code WebSphereExtendedJtaPlatform} implementations for various Hibernate
		 * versions.
		 */
		private static final String WEBSHERE_JTA_PLATFORM_CLASSES[] = {
				"org.hibernate.engine.transaction.jta.platform.internal.WebSphereExtendedJtaPlatform",
				"org.hibernate.service.jta.platform.internal.WebSphereExtendedJtaPlatform", };

		@Autowired
		private JpaProperties properties;

		@Autowired
		private DataSource dataSource;

		@Override
		protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
			return new HibernateJpaVendorAdapter();
		}
              
                ...
     }

Perceba que várias annotations foram usadas em cima da classe HibernateJpaAutoConfiguration, vamos analisar cada uma delas para que tudo fique mais claro. A mais comum é @Configuration, que é utilizada quando queremos indicar que uma classe contém métodos que produzem beans. Caso fique mais curioso, vá na classe mãe e veja os métodos anotados com @Bean. 

Uma outra interessante é a @ConditionalOnClass, que como o próprio nome diz, é utilizada para verificar se determinada classe está no classpath do projeto. Faz até bastante sentido, já que se não tiver tais classes não faz sentido configurar nada da JPA. Além dessa annotation uma outra importante é a @Conditional. Ela recebe como argumento uma classe que implementa a interface Condition, do próprio Spring, e que é utilizada para executar algum código baseada em alguma condição. Vamos dar uma olhada na classe HibernateEntityManagerCondition.

	//classe interna
	static class HibernateEntityManagerCondition extends SpringBootCondition {

		private static String[] CLASS_NAMES = {
				"org.hibernate.ejb.HibernateEntityManager",
				"org.hibernate.jpa.HibernateEntityManager" };

		@Override
		public ConditionOutcome getMatchOutcome(ConditionContext context,
				AnnotatedTypeMetadata metadata) {
			for (String className : CLASS_NAMES) {
				if (ClassUtils.isPresent(className, context.getClassLoader())) {
					return ConditionOutcome.match("found HibernateEntityManager class");
				}
			}
			return ConditionOutcome.noMatch("did not find HibernateEntityManager class");
		}
	}

Simplesmente ela verifica se a implementação do EntityManager, provida pelo Hibernate, está no classpath. Ao invés de implementar a interface Condition diretamente, ela herda da interface SpringBootCondition. A ideia é que caso a condição não seja atendida um log mais claro seja escrito, para que o programador possa ter uma visão mais clara de como resolver o problema. Apenas para matar a curiosidade, vamos dar uma olhada dentro da annotation @ConditionalOnClass. 

	@Conditional(OnClassCondition.class)
	public @interface ConditionalOnClass {

		/**
		 * The classes that must be present. Since this annotation parsed by loading class
		 * bytecode it is safe to specify classes here that may ultimately not be on the
		 * classpath.
		 * @return the classes that must be present
		 */
		Class<?>[] value() default {};

		/**
		 * The classes names that must be present.
		 * @return the class names that must be present.
		 */
		String[] name() default {};

	}

Ela é só um atalho para uma @Condition que usa uma implementação pronta para checar se uma classe existe ou não no classpath. Isso é muito usado no Spring, annotations representam a composição de várias outras para que a configuração fique mais fácil para os programadores.

Para fechar a análise das annotations, faltou apenas darmos uma olhada na @AutoConfigureAfter. Como o próprio nome informa, ele diz para o Spring Boot que a classe HibernateJpaAutoConfiguration deve ser usada para criar as configurações apenas depois que a classe DataSourceAutoConfiguration for executada.

Para não passar desapercebido, dê também uma olhada no fonte da classe para você ver que realmente a maioria dos métodos que a gente se acostumou a configurar para ter o Hibernate/JPA funcionando, já está escrito dentro da AutoConfiguration provida pelo Spring Boot.

Esse foi um post que passeou um pouco mais por dentro do Spring Boot, algo que não afeta diretamente no seu dia a dia de desenvolvimento. De todo jeito, quando mais você souber do que acontece por dentro das tecnologias, mais preparado você fica para lidar com algum problema que possa aparecer.

Deployando sua aplicação com o Spring Boot

Já escrevi aqui no blog, duas vezes, o quanto o Spring Boot pode nos ajudar no desenvolvimento das aplicações. Levou ao extremo o termo convenção sobre configuração e realmente deixou tudo muito mais simples. Um ponto que eu ainda não comentei foi sobre o processo de deploy, se você tiver escolhido o JSP como tecnologia de view. Caso tenha escolhido outras, como o ascendente Thymeleaf, o processo de deploy é o mais simples possível, já que é só gerar o jar e rodar. Em outro post eu comento porque eu escolhi não trocar de tecnologia de view.

O processo até é bem documentado no site, mas acho que alguns pontos merecem um reforço. O primeiro é justamente o fato de termos escolhido JSP como view. O Spring Boot configura o Tomcat para sempre procurar as páginas a partir do caminho src/main/webapp, relativo ao ponto de execução do seu jar. Só que esses arquivos não são copiados quando você executa um mvn clean install, ou seja, você precisa colocar no seu script de deploy um passo para copiar a estrutura e jogar no mesmo local do seu jar executável. Caso você seja curioso, dê uma olhada nesse commit e veja os valores fixos na classe.  A própria documentação sugere que você não deve usar o jar executável quando estiver com jsps.

Para contornar esse problema e deixar, pelo menos para mim, tudo um pouco mais simples, podemos recorrer a um outro plugin do maven que também roda um tomcat embedded, só que referenciando um war.  O webapp-runner gera para você um jar que sobe o tomcat e permite que você passe como argumento o caminho para um arquivo war.

java -jar dependency/webapp-runner.jar tudosobreesporte-1.0.0-SNAPSHOT.war

Para usá-lo, basta que você adicione a seguinte entrada no seu pom.xml. 

	<plugin>
		<groupId>org.apache.maven.plugins</groupId>
		<artifactId>maven-dependency-plugin</artifactId>
		<executions>
			<execution>
				<phase>package</phase>
				<goals>
					<goal>copy</goal>
				</goals>
				<configuration>
					<artifactItems>
						<artifactItem>
							<groupId>com.github.jsimone</groupId>
							<artifactId>webapp-runner</artifactId>
							<version>7.0.57.2</version>
							<destFileName>webapp-   runner.jar</destFileName>
						</artifactItem>
					</artifactItems>
				</configuration>
			</execution>
		</executions>
	</plugin>

Além disso você precisa fazer só mais algumas alterações no seu pom.xml. Quando o SetupMyProject gera o seu projeto para o Spring Boot, o tipo de projeto é jar, justamente porque é o que precisa para você subir uma aplicação com o Spring Boot em dev. Você precisa trocar o tipo para war.

  <packaging>war</packaging>

Outro detalhe importante é que precisamos apagar o plugin do Spring Boot que gera um fat jar quando executamos um mvn install. 

        <!-- apague esse trecho -->
	<plugin>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-maven-plugin</artifactId>
	</plugin>

Só que agora surgiu um possível problema. Quando gerávamos o jar com tudo dentro, era também incorporado uma classe específica do Spring Boot que usava a classe do nosso projeto que possuía o método main, geralmente aquela que chamamos de Boot,  para conseguir dar o start no nosso projeto. Isso é tão verdade que se você tiver mais de uma classe no projeto com o método main, dá erro de execução quando vamos rodar o mvn install com o plugin do bootAgora que temos um war, que vai ser carregado normalmente pelo Servlet Container, precisamos voltar a estratégia padrão do Spring MVC, ou seja, é necessário ter uma classe que implemente a interface WebApplicationInitializer. 

Para a nossa sorte o Spring Boot já possui uma implementação dessa interface justamente pensando nesse estilo de deploy :). Basta que a sua classe que tem o main e que, geralmente, também tem a annotation SpringBootApplication, herde de SpringBootServletInitializer.  Não teria problema alguma se você optasse por criar uma classe separada e herdasse também, fiz direto na classe Boot apenas pela facilidade.

  public class Boot extends SpringBootServletInitializer {
    ...
  }

Para fechar, precisamos indicar para a classe SpringBootServletInitializer quais as nossas classes responsáveis pela configuração do nosso projeto.

	@Override
	protected SpringApplicationBuilder configure(
			SpringApplicationBuilder application) {
		return application.sources(Boot.class);
	}

Repare que eu passei como argumento a própria classe anotada com SpringBootApplication, já que essa annotation já define que todas as classes, a partir do pacote onde ela está declarada, devem ser scaneadas.

Agora que já ajustamos o código precisamos só prestar atenção em último detalhe. O tempo inteiro numa aplicação com o Spring Boot usamos o arquivo application.properties. Só que quando vamos para produção, em geral vamos ter outras configurações. Alguns exemplos:

  1. Nome e dados de acesso do banco de dados
  2. Caminhos para escrita de arquivos

Essa ideia de environment já é bem suportada pelo Spring e o Spring Boot tira apenas proveito. Na hora que você passe o argumento -Dspring.profiles.active atribuindo o valor de ambiente que você quer.

  java -Dspring.profiles.active=production -jar webapp-runner.jar tudosobreesporte-1.0.0-SNAPSHOT.war& 

No exemplo acima você está ensinando ao Spring Boot a procurar por um arquivo chamado application-production.properties :).

É importante ressaltar que você pode continuar, em dev, executando a classe Boot. A única diferença é que em ambiente de produção você agora roda um war convencional.

O post de hoje teve mais o objetivo de centralizar algumas informações importantes sobre deploy de uma aplicação usando o Spring Boot. Como já comentei, o mundo do Spring é gigante e, se tiver algum assunto que você esteja mais interessado, não deixe de comentar ;).

 

Ficando ainda mais produtivo com o Spring Boot

Já escrevi aqui no blog sobre o Spring Boot e comentei que achava uma ótima ferramenta e que torcia para que ela influenciasse outros projetos Java mundo a fora. Um dos starters que mais gosto é o Dev Tools, pois diminui bastante o tempo que devemos ficar esperando para cada alteração que fazemos no nosso projeto.

Isso por si só já seria suficiente, mas aí é que entra a parte que você quer tirar o máximo de proveito do seu ambiente. Caso você rode o seu projeto em modo de debug, a própria máquina virtual consegue aplicar as alterações que você faz dentro de seus métodos sem precisar reiniciar nada. Só que habilitando o Dev Tools você acaba perdendo isso, já que o Spring Boot vai recarregar sempre que alguma classe foi alterada. Para resolver isso, podemos adicionar uma propriedade no arquivo application.properties chamada spring.devtools.restart.trigger-file. A ideia é que você crie um arquivo que, quando alterado, faz o Spring Boot reiniciar. Abaixo temos um exemplo:

  spring.devtools.restart.trigger-file=.reloadtrigger

Apenas lembrando que você deve criar esse arquivo dentro, por exemplo, da pasta src/main/resources, apenas para ficar no classpath e ser monitorado. Isso, pelo menos na minha opinião, já ajuda. Você só precisa atualizar o arquivo quando alterar alguma coisa da estrutura do seu projeto em si, alguns exemplos:

  1. Criação de novos controllers;
  2. Adição de novas propriedades no application.properties
  3. Criação de novas configurações;
  4. Criação de novas classes ou interfaces;

Nesse momento já temos um cenário onde tiramos o melhor, entre as nossas possibilidades. Usamos o modo debug para que nossas alterações dentro de métodos sejam refletidas imediatamente na aplicação e usamos o Dev Tools para refletir as outras mudanças.

A partir daqui é apenas um relato de alguém que queria ir mais além e economizar alguns segundos a mais. Sempre que era necessário reiniciar a aplicação eu tinha que fazer um ctrl+shift+r, digitar o nome do arquivo e ainda alterá-lo, para que o Spring Boot percebesse a alteração e fizesse o trabalho dele. Sem contar que para cada projeto novo eu teria que criar o arquivo e tudo mais.

O primeiro passo é criar o arquivo uma vez só. Para fazer isso eu criei uma pasta chamada .spring-boot e criei lá dentro um arquivo chamado restart.txt. Agora é necessário fazer o Spring Boot também considerar esse endereço quando for levantar a aplicação. Para fazer isso basta adicionar a propriedade spring.devtools.restart.additional-paths apontando para o caminho da pasta que você acabou de criar. Outra opção é criar um arquivo chamado .spring-boot-devtools.properties na sua home, por exemplo no mac seria em /Users/seuUsuario. Toda vez que o Spring Boot é iniciado ele vai ler este arquivo também, caso ele exista.

O ponto a ser resolvido agora é como atualizar o arquivo de maneira rápida. Aqui vai variar de ambiente para ambiente. No mac eu usei o aplicativo chamado Keyboard Maestro e criei o atalho cmd+shift+b para que sempre que for necessário, eu altero a data de atualização do arquivo, forçando o Spring Boot a recarregar minha aplicação. No Windows você pode usar o AutoHotKey ou qualquer outro que você conheça. No Linux(Ubuntu pelo menos), você pode customizar um atalho para que ele fique alterando o arquivo para você.

Agora eu fiquei com um ambiente de desenvolvimento o mais próximo possível do que eu experimentei quando usei Ruby. Faço muitas alterações sem reiniciar nada e, quando é necessário, espero uns 2 segundos e tudo está pronto para ser usado.  Mesmo que você que não queira ir para o modo de debug, eu ainda prefiro usar o Spring Boot com o trigger file, já que várias vezes apertamos ctrl+s desesperadamente.

[OFF-TOPIC] Geração de CRUDs automática no SetupMyProject

Ontem colocamos no ar uma nova versão do SetupMyProject. A maior novidade é que agora você tem a possibilidade de gerar o seu projeto já com um cadastro completo de qualquer entidade que você queira, os famosos CRUDs. Todos nós sabemos fazer um CRUD, isso não é segredo, o problema é o tempo que perdemos para fazer o trabalho. Criar as telas, entidades, daos e validações consomem um certo tempo. Todo o processo é realizado online, como já acontece atualmente, não queremos que você precise instalar nem configurar nada para ter acesso a funcionalidade.

Um decisão que tivemos que tomar foi: como o usuário vai entrar com os dados necessários para que nós possamos gerar o cadastro dele? Com certeza esse não foi um caminho fácil de ser decidido, mas por hora ficamos com a opção da escrita de um xml .

    <models>
	  <model name="Product">
		  <field name='name' type='java.lang.String'/>
		  <field name='description' type='java.lang.String' />
		  <field name='price' type='java.math.BigDecimal' />
       </model>
    </models>

No exemplo acima, solicitamos a geração de um cadastro para uma classe chamada Product e, além disso, especificamos quais são os atributos e tipos que queremos para ela. Para facilitar a escrita do xml, criamos uma documentação no github, além de fazer a validação do xml com um xsd para garantir que a estrutura mínima foi obedecida. Dá para ir um pouco além e gerar cadastros mais completos.

      <models>
         <model name="Product">
		   <field name='name' type='java.lang.String'/>
		   <field name='description' type='java.lang.String' />
		   <field name='price' type='java.math.BigDecimal' />
		   <field name='category' type='Category' javaType='false' formInputType="select" formInputName='category.id' selectLabelField="name" />
         </model>
         <model name="Category">
	        <field name='name' type='java.lang.String' />
	        <field name='description' type='java.lang.String' />
         </model>
      </models>

Na situação acima é informado a necessidade de dois cadastros. Especificamente na tela de produtos, informamos que queremos um select para que o usuário possa escolher a categoria do produto.  Perceba que para as entidades específicas do usuário não é necessário especificar o nome completo da classe, já que ela vai ficar em um pacote chamado models, relativo ao pacote base do projeto. Por exemplo, br.com.empresa.novoprojeto.models.Category. Um próximo passo é suportar que seja gerado uma relação do tipo oneToMany, já que por enquanto a geração automática só suporta relações do tipo ManyToOne. 

Por fim, existem situações onde não queremos a geração de um controller e uma view para cadastro, é o caso de uma entidade do tipo Checkout num sistema de e-commerce. Geralmente esse objeto é gerado a partir de um carrinho de compras. Para esse tipo de situação, basta que seja adicionado o atributo viewController.

      <models>
         <model name="Product">
	       <field name='name' type='java.lang.String'/>
	       <field name='description' type='java.lang.String' />
	       <field name='price' type='java.math.BigDecimal' />
	       <field name='category' type='Category' javaType='false' formInputType="select" formInputName='category.id' selectLabelField="name" />
        </model>
        <model name="Category">
	        <field name='name' type='java.lang.String'/>
	        <field name='description' type='java.lang.String' />
        </model>
        <model name="User">
	        <field name='login' type='java.lang.String'/>
	        <field name='password' type='java.lang.String' />
        </model>
		<model name="Checkout" viewController="false">
	        <field name='value' type='java.math.BigDecimal'/>
	        <field name='user' type='User' javaType="false" />
		</model>
     </models>

Como não poderia deixar de ser, as opções de projetos com SpringMVC direto e Spring Boot já suportam a geração de CRUDs. Ficaremos muito felizes se vocês puderem ir lá e testar :). Aproveitando o espaço, também geramos cadastros para projetos baseados em JSF 2.2 e VRaptor 4, tem para todo gosto. Um último toque que adicionamos foi a geração do readme do projeto, para os passos finais de configuração dos plugins que foram selecionados :).

Para tentar ficar mais perto do desenvolvimento do projeto, e não só do começo, a nossa próxima feature é que você consiga gerar configurações de plugins, ou cadastros adicionais, para projetos que já estão em andamento!

Começando com a magia do Spring Boot

No livro sobre Spring que escrevi para a Casa do Código e no SetupMyProject fiz a opção pelo uso normal das extensões do Spring. Para criar o projeto web, usei o Spring MVC no seu modo convencional configurando tudo que era necessário, assim como fiz para a parte da JPA, Spring Security etc. No fim acho realmente importante que as pessoas conheçam bem as tecnologias que usam, para terem um olhar crítico sobre a ferramenta e poderem achar os pontos de extensão necessários para os projetos do dia a dia.

Na época da escrita do livro, o time do Spring já tinha lançado uma outra extensão chamada Spring Boot, cujo objetivo era acelerar o processo de configuração da aplicação, estabelecendo configurações defaults baseadas nas experiências dos desenvolvedores do próprio Spring, assim como pelo feedback provido pelos usuários do framework. Optei por não usá-lo, justamente pelos motivos citados no primeiro parágrafo. Agora, com tudo mais sedimentado e com um conhecimento realmente bom do framework resolvi que era hora de começar a construir uma aplicação com o Spring Boot, já que, principalmente durante a escrita do livro, fui obrigado a navegar muito pelos internals do Spring e tudo mais. Peguei como exemplo a própria app do livro, ela possui regras boas o suficiente para me fazer passar por diversos problemas. O código inicial já está no github.

Então vamos começar! O primeiro passo, como não poderia ser diferente, é a criação do projeto. Você pode realizar este passo no próprio SetupMyProject. O zip vai vir com o mínimo configurado, mas é aí que você já vai perceber a diferença. Para conseguir subir o servidor e acessar a primeira rota, só precisamos de uma classe configurada no projeto.

	@SpringBootApplication
	@Controller
	public class CasadocodigoSpringbootApplication {

		@RequestMapping("/")
		@ResponseBody
		public String index(){
			return "funciona?";
		}

		public static void main(String[] args) {
			SpringApplication.run(CasadocodigoSpringbootApplication.class, args);
		}
	}

Aqui já temos um pouco de mágica. Você vai rodar sua aplicação web a partir de um bom e velho método main. A classe SpringApplication vai ler as configurações da classe passada como argumento e pronto, você tem a sua aplicação no ar. A outra parte da mágica acontece por conta da annotation @SpringBootApplication. Ela é um Sterotype do Spring Boot que já encapsula algumas outras annotations, como a @EnableAutoConfiguration. Essa última, por sua vez, carrega a AutoConfigurationPackages que é responsável por configurar os pacotes que devem ser escaneados, baseados na localização da sua classe.

Um outro ponto bem impressionante é o pom.xml criado, você vai perceber que ele possui pouquíssimas dependências! Vamos dar uma rápida olhada.

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.3.0.BUILD-SNAPSHOT</version>
	</parent>	

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
	</dependencies>

Perceba que você adiciona algumas dependências que eles chamam de starters. A grande sacada é fazer com esses artefatos já baixem tudo que você precisa. E aqui, eu preciso dizer que, pelo menos para mim, eles tomaram uma decisão bastante acertada. Ao invés de te dar a opção de escolher entre vários frameworks, servidores web, libs específicas e etc, eles tomaram algumas decisões e empurraram pra gente. Para a maioria dos projetos isso não muda e, se for preciso, você pode sobreescrever tudo que eles fizeram. A motivação deles foi a mesma que tivemos ao construir o SetupMyProject. Já estamos cheios de frameworks, precisamos é de uma maneira mais fácil de usá-los.

Por mais que já tenhamos o projeto configurado, provavelmente esse não é bem o modelo que vamos seguir. Os nossos controllers não vão ser declarados dentro da mesma classe de configuração. O normal é criar uma outra classe que representa este seu controller.

        @Controller
	@Transactional
	@RequestMapping("/produtos")
	public class ProductsController {

		@Autowired
		private ProductDAO products;
		@Autowired
		private FileSaver fileSaver;
		@Autowired
		private ServletContext ctx;
		@Autowired
		private InternalResourceViewResolver resolver;

		@RequestMapping(method=RequestMethod.GET)
		@Cacheable(value="lastProducts")
		public ModelAndView list() throws ServletException, IOException{
			System.out.println("ola");
			ModelAndView modelAndView = new ModelAndView("products/list");
			modelAndView.addObject("products", products.findAll());
			return modelAndView;
		}

		...

	}

E agora é só pedir para escanear o pacote… Opa, não precisa mais! Como eu criei esse classe em um pacote abaixo do pacote da classe de configuração, todas as classes já vão ser escaneadas de maneira automática. Agora é necessário configurar o acesso a JPA e, nesse momento, nossos corações vão ficar felizes novamente. O nosso único trabalho é adicionar um starter no pom.xml. 

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-data-jpa</artifactId>
	</dependency>

Quase tudo que você acha que deveria fazer para configurar o acesso ao banco com a JPA, vai ser feito automagicamente. Não será necessário configurar persistence.xml, EntityManagerFactory, EntityManager, PlatformTransactionManager nem nada, tudo vai ser feito pra você. A única coisa que você precisa entregar a ele é o DataSource com os  dados de acesso ao banco. Podemos criar um método com essa configuração na própria classe que contém nosso main. 

@SpringBootApplication
	public class CasadocodigoSpringbootApplication {
		@Bean
		public DataSource dataSource(Environment environment) {
			DriverManagerDataSource dataSource = new DriverManagerDataSource();
			dataSource.setDriverClassName("com.mysql.jdbc.Driver");
			dataSource.setUrl("jdbc:mysql://localhost:3306/casadocodigo");
			dataSource.setUsername("root");
			dataSource.setPassword("");
			return dataSource;
		}

		public static void main(String[] args) {
			SpringApplication.run(CasadocodigoSpringbootApplication.class, args);
		}
	}

Além disso, ele também já inclui as dependências para a Spring Data JPA, para facilitar a criação dos seus DAOs. Para completar essa primeira parte da migração do projeto, foi necessário fazer uma pequena configuração extra para os jsps. O Spring Boot sugere que você use outra tecnologia de view, mas como o jsp era a utilizada no projeto do livro, eu resolvi ir com ela mesmo. Como o tomcat está sendo executado de forma embedded, é necessário que adicionemos uma nova dependência no pom, que é a do compilador de jsps para este tipo de cenário.

	<dependency>
		<groupId>org.apache.tomcat.embed</groupId>
		<artifactId>tomcat-embed-jasper</artifactId>
		<scope>provided</scope>
	</dependency>

Além disso, como é procedimento normal, precisamos configurar um prefixo e um sufixo para busca da nossa view. Podemos fazer isso do jeito tradicional, adicionando um método que retorna um InternalResourceViewResolver. Só que podemos aproveitar a oportunidade para já apresentar um outro detalhe que você pode tirar proveito, que são os arquivos de configuração externos. Você pode criar um arquivo chamado application.properties na raiz do seu classpath, geralmente em src/main/resources. Lá você pode colocar configurações específicas das tecnologias utilizadas no projeto, abaixo segue o meu exemplo.

  spring.mvc.view.prefix=/WEB-INF/jsp/
  spring.mvc.view.suffix=.jsp

  #hibernate
  spring.jpa.hibernate.ddl-auto=update

Perceba que ele é bem diferente de um spring-context.xml da vida. Você não tem que declarar um bean para ser usado, simplesmente configura as propriedades do bean já escolhido. 

Para fechar com chave de ouro, pelo menos para mim :), eles deram mais um passo pensando na vida fácil do usuário. Nessa semana eles lançaram um novo starter chamado de DevToolsUma facilidade adicionada foi a de recarregamento do projeto para cada mudança que você faz no código. Só que, como eles mesmos disseram, esse recarregamento é mais esperto do que um simples hot deploy de um servidor web tradicional. Além disso, ainda adicionaram o suporte para o Live Reloadque possibilita o recarregamento automático das páginas no seu navegador sempre que você alterar uma classe sua no servidor!

Ufa! Falei bastante hoje :). Acho o Spring Boot um projeto muito útil e que, com certeza, você devia dar uma olhada. É um approach que por muito eu esperava no mundo Java e espero que influencie outros projetos a fazer o mesmo. Por fim, acho importante lembrar que você deve continuar procurando o motivo do acontecimento das coisas, para não ficar um “programador de Spring Boot” ao invés de um programador  Java.