Solucionado (ver solução)
Solucionado
(ver solução)
9
respostas

(Aula 9) Upload de arquivo não funciona! Atributo "sumario" nulo.

Boa tarde, galera!

Estou com um problema ao selecionar um arquivo pelo <input type="file" name="sumario"/> e clicar no botão de submit da página "form.jsp".

O que acontece após submeter o formulário é que o argumento MultipartFile sumario chega nulo na chamada do método gravar.

Além disso, a partir do momento que incluo o MultipartFile sumario na assinatura do método gravar, o bindingResult.hasErrors() retorna true e acusa erros em todos os campos da tela, como se não estivessem preenchidos.

Meus fontes:

ProdutoController.java

@RequestMapping(method=RequestMethod.POST)
    public ModelAndView gravar(MultipartFile sumario, @Valid Produto produto, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
        if (bindingResult.hasErrors()) {
            return new ModelAndView("produtos/form");
        }

        System.out.println(sumario.getOriginalFilename());

        dao.gravar(produto);
        redirectAttributes.addFlashAttribute("sucesso", "Produto cadastrado com sucesso!");

        return new ModelAndView("redirect:/produtos");
    }

form.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Cadastro de Produtos</title>
</head>
<body>
    <form:form action="${s:mvcUrl('PC#gravar').build()}" method="post" enctype="multipart/form-data" commandName="produto">
        <div>
            <label>
                Titulo: 
            </label>
            <form:input path="titulo"/>
            <form:errors path="titulo" />
        </div>
        <div>
            <label>
                Descrição: 
            </label>    
            <form:textarea path="descricao" rows="10" cols="20"/>
            <form:errors path="descricao" />
        </div>
        <div>
            <label>
                Número de páginas: 
            </label>
            <form:input path="paginas"/>
            <form:errors path="paginas" /> 
        </div>
        <div>
            <label>Data de Lançamento:</label>
            <form:input path="dataLancamento"/>
            <form:errors path="dataLancamento"/>
        </div>
        <c:forEach items="${tipos}" var="tipoPreco" varStatus="status">
            <div>
                <label>${tipoPreco}</label>
                <form:input path="precos[${status.index}].valor"/>
                <form:hidden path="precos[${status.index}].tipo" value="${tipoPreco}"/>
            </div>
        </c:forEach>
        <div>
            <label>Arquivo:</label>
            <input type="file" name="sumario"/>
        </div>

        <button type="submit">Cadastrar o produto</button>
    </form:form>
</body>
</html>

ServletSpringMvc.java

package br.com.casadocodigo.loja.conf;

import javax.servlet.Filter;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletRegistration.Dynamic;

import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class ServletSpringMvc extends AbstractAnnotationConfigDispatcherServletInitializer{

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return null;
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[] {AppWebConfiguration.class, JPAConfiguration.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] {"/"};
    }

    @Override
    protected Filter[] getServletFilters() {
        CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
        encodingFilter.setEncoding("UTF-8");
        return new Filter[] {encodingFilter};
    }

    @Override
    protected void customizeRegistration(Dynamic registration) {
        registration.setMultipartConfig(new MultipartConfigElement(""));
    }

}

AppWebConfiguration.java

package br.com.casadocodigo.loja.conf;

import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.format.datetime.DateFormatter;
import org.springframework.format.datetime.DateFormatterRegistrar;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

import br.com.casadocodigo.loja.controllers.HomeController;
import br.com.casadocodigo.loja.daos.ProdutoDAO;
import br.com.casadocodigo.loja.infra.FileSaver;

@EnableWebMvc
@ComponentScan(basePackageClasses={HomeController.class, ProdutoDAO.class, FileSaver.class})
public class AppWebConfiguration {

    @Bean
    public InternalResourceViewResolver internalResourceViewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");

        return resolver;
    }

    @Bean
    public MessageSource messageSource(){
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setBasename("/WEB-INF/messages");
        messageSource.setDefaultEncoding("UTF-8");
        messageSource.setCacheSeconds(1);
        return messageSource;
    }

    @Bean
    public FormattingConversionService mvcConversionService() {
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
        DateFormatterRegistrar registra = new DateFormatterRegistrar();
        registra.setFormatter(new DateFormatter("dd/MM/yyyy"));
        registra.registerFormatters(conversionService);

        return conversionService;

    }

    @Bean
    public MultipartResolver multipartResolver() {
        return new StandardServletMultipartResolver();
    }
}

Agradeço se alguém puder me ajudar!

9 respostas

Oi, Leonardo. Nâo vi nada estranho nesses que você postou. Poste o FileSaver, o Produto e o ProdutoDao também.

Oi, Skywalker.

Acho que o problema está antes de chegar no FileSaver e no DAO. Ele não chega no breakpoint que eu coloquei no método do FileSaver. E quando o debug está no método gravar, o MultipartFile fica nulo.

Mas, em todo caso, aqui estão as outras classes:

Produto.java

package br.com.casadocodigo.loja.models;

import java.util.Calendar;
import java.util.List;

import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

import org.springframework.format.annotation.DateTimeFormat;

@Entity
public class Produto {

    @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    private String titulo; 

    private String descricao; 

    private Integer paginas;

    @ElementCollection
    private List<Preco> precos;

    @DateTimeFormat
    private Calendar dataLancamento;

    private String sumarioPath;

    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public Calendar getDataLancamento() {
        return dataLancamento;
    }
    public void setDataLancamento(Calendar dataLancamento) {
        this.dataLancamento = dataLancamento;
    }

    public String getTitulo() {
        return titulo;
    }
    public void setTitulo(String titulo) {
        this.titulo = titulo;
    }
    public List<Preco> getPrecos() {
        return precos;
    }
    public void setPrecos(List<Preco> precos) {
        this.precos = precos;
    }
    public String getDescricao() {
        return descricao;
    }
    public void setDescricao(String descricao) {
        this.descricao = descricao;
    }
    public Integer getPaginas() {
        return paginas;
    }
    public void setPaginas(Integer paginas) {
        this.paginas = paginas;
    }
    public String getSumarioPath() {
        return sumarioPath;
    }
    public void setSumarioPath(String sumarioPath) {
        this.sumarioPath = sumarioPath;
    }

    @Override
    public String toString() {
        return "Produto [titulo=" + titulo + ", descricao=" + descricao + ", paginas=" + paginas + "]";
    }
}

FileSaver.java

package br.com.casadocodigo.loja.infra;

import java.io.File;
import java.io.IOException;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

@Component
public class FileSaver {

    @Autowired
    private HttpServletRequest request;

    public String salvar(MultipartFile multipartFile, String baseFolder) {
        try {
            String realPath = request.getServletContext().getRealPath("/" + baseFolder);
            String path = realPath + "/" + multipartFile.getOriginalFilename();
            multipartFile.transferTo(new File(path));

            return baseFolder + "/" + multipartFile.getOriginalFilename();
        } catch (IllegalStateException | IOException e) {
            throw new RuntimeException(e);
        }
    }

}

ProdutoDAO.java

package br.com.casadocodigo.loja.daos;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import br.com.casadocodigo.loja.models.Produto;

@Repository
@Transactional
public class ProdutoDAO {

    @PersistenceContext
    private EntityManager entityManager;

    public void gravar(Produto produto) {
        entityManager.persist(produto);
    }

    public List<Produto> listar(){
        return entityManager.createQuery("from Produto p", Produto.class).getResultList();
    }

}

Entendi. Está faltando setar o sumarioPath no produto antes de gravar.

@RequestMapping(method=RequestMethod.POST)
    public ModelAndView gravar(MultipartFile sumario, @Valid Produto produto, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
        if (bindingResult.hasErrors()) {
            return new ModelAndView("produtos/form");
        }

        System.out.println(sumario.getOriginalFilename());

    String path = fileSaver.write("arquivos-sumario", sumario);
        produto.setSumarioPath(path);
        dao.gravar(produto);
        redirectAttributes.addFlashAttribute("sucesso", "Produto cadastrado com sucesso!");

        return new ModelAndView("redirect:/produtos");
    }

e filesaver tem que ser um atributo @Autowired no ProdutoController

    @Autowired
    private FileSaver fileSaver;

Ah, corrigindo: String path = fileSaver.salvar(....);

solução!

Valeu pela ajuda, Skywalker!

Cara, era outra coisa. Esses códigos eu não tinha colocado ainda porque os exercícios seguintes tratam disso.

Mas agora resolvi!

O problema era relacionado à versão do meu servidor. Eu estava usando o Wildfly 8 e quando eu troquei para o Wildfly 10 para fazer um teste, funcionou. Acho que o erro acontecia por ter algo relacionado com a versão do servlet-api, talvez no Wildfly 8 a versão anterior era suportada. Ou algo assim.

Como assim? O sumario foi salvo no produto sem fazer o produto.setSumarioPath(sumarioPath) antes de dao.gravar(produto)? Se for isso eu n'ao entendo mais nada... foi mesmo para a base de dados ou só apareceu no log console?

Não, eu coloquei isso depois que vi que o MultipartFile estava vindo preenchido. E coloquei a chamada do fileSaver também. Agora o meu código está completo e funcionando.

Ah sim, faz sentido. que bom que ta tudo funcionanndo!