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

One thought on “Abstraindo o download de arquivos no Spring MVC

  1. Excelente, Alberto! Solução simples e funcional!

    O que não consigo entender é que o SpringMVC ainda hoje não nos dê uma solução básica para download de arquivos – chega a ser vergonhoso [se bem que JSF e muitos outros frameworks não nos dão]. Não faz o menor sentido o desenvolvedor ter que utilizar API baixo-nível do framework para fazer um download de arquivo, não em 2016!

    Se você tiver diversos formatos para disponibilizar um arquivos (File, InputStream, bytes[] etc) creio que vocês podem brincar com uma Builder mais esperta e reaprovietável, algo como:

    DownloadEntity download downloadBuilder.of(meuArquivo)
        .withFileName("curriculo.txt") // opcional, o padrão é File.getName()
        .withContentType("text/plain") // optional, não será enviado se nulo
        .downloadable() // opcional, o padrão é inline content
        .build();
    

    O próprio VRaptor4 faz isso e acho mais interessante que decorar os nomes dos diversos tipos de classes: FileDownload, ByteArrayDownload, InputStreamDownload etc.

    Um abraço,

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s