Abstraindo o download de arquivos no Spring MVC

Há um tempo atrás, escrevi um post sobre como realizar download de arquivos utilizando o Spring MVC. Rafael Ponte,  um amigo meu, empreendedor/programador que mora em Fortaleza, perguntou se não tinha um jeito mais fácil de realizar o mesmo processo. Basicamente ele queria que tivesse uma abstração melhor para indicar um download, algo como o que segue:

	@RequestMapping(value = "/downloads/{code}", 
		method = RequestMethod.GET)
	public FileDownload dowload(@PathVariable("code") String code,
			@AuthenticationPrincipal User user) {

		Optional<Course> foundCourse = courseDao.findByUserEmailAndCode(
				user.getEmail(), code);

		if (!foundCourse.isPresent()) {
			return ResponseEntity.notFound().build();
		}

		Book book = bookDao.find(foundCourse.get().getCode());
		return new FileDownload(...);
	}

Pronto, eu não encontrei nada. Confesse que não procurei muito, e, ainda acho, que existe a chance de ter algo mais elegante e que eu não conheça. De todo jeito eu caí no mesmo problema, trabalhando em um projeto interno aqui na Caelum e a pessoa que estava pareando comigo fez o mesmo questionamento :/. Nesse momento paramos para pensar um pouco mais e chegamos na seguinte solução de código:

@RequestMapping(value = "/downloads/{code}", 
	method = RequestMethod.GET)
public HttpEntity<?> dowload(@PathVariable("code") String code,
		@AuthenticationPrincipal User user) {

	Optional<Course> foundCourse = courseDao.findByUserEmailAndCode(
			user.getEmail(), code);
	if (!foundCourse.isPresent()) {
		return ResponseEntity.notFound().build();
	}

	Book book = bookDao.find(foundCourse.get().getCode());
	return new DownloadEntity(book.generateDrmVersion(user), 
		code + ".pdf");
}

Basicamente alcançamos a mesma abstração que Rafael tinha sugerido :). A sacada é que um dos possíveis retornos do seu método é um objeto cuja classe seja igual ou filha de HttpEntity. Herdamos dela e criamos nossa abstração para representar um download.

package br.com.caelum.alumni.infra.spring;

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;

public class DownloadEntity extends HttpEntity<byte[]> {

	private HttpEntity<byte[]> httpEntity;

	public DownloadEntity(byte[] bytes, String fileName) {
		HttpHeaders httpHeaders = new HttpHeaders();
		httpHeaders.add("Content-disposition", "attachment; filename=\""+fileName+"\"");
		httpEntity = new HttpEntity<byte[]>(bytes, httpHeaders);
	}

	public HttpHeaders getHeaders() {
		return httpEntity.getHeaders();
	}

	public byte[] getBody() {
		return httpEntity.getBody();
	}

	public boolean hasBody() {
		return httpEntity.hasBody();
	}

	public boolean equals(Object other) {
		return httpEntity.equals(other);
	}

	public int hashCode() {
		return httpEntity.hashCode();
	}

	public String toString() {
		return httpEntity.toString();
	}
}

Herdamos de HttpEntity e simplesmente delegamos as chamadas dos métodos para o HttpEntity que criamos internamente no nosso objeto. Uma solução simples e que você pode reaproveitar para vários projetos seus.

Acho que esse é um passo muito importante quando falamos de programação, no caso aqui estamos usando orientação a objetos. Precisamos ser capazes de aplicar os conceitos que aprendemos em qualquer situação, não importa se o código é nosso ou provido por algum framework. Tem uma interface, você pode implementar, ta vendo uma classe que não é marcada como final, você pode herdar! O foco é a solução e a facilidade de manutenção :).

Advertisements