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

Aula 09 - FilePath não é inserido e form não redireciona

Olá, estava indo bem no curso Spring MVC I até chegar na parte de submissão de arquivo via formulário.

O caminho para o arquivo não é inserido na pasta existente, e ainda mesmo após o formulário ser submetido com todos os dados preenchidos e o arquivo selecionado, o mesmo não é redirecionado para a view "/produtos" como anteriormente.

Já bati cabeça com isso aqui algum tempo, logo vou pedir ajuda colocando aqui o meu código atual. O mesmo também está disponível no meu github https://github.com/DaviGadelhaLeitao/book-store

package br.com.bookstore.controller;

import java.util.List;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import br.com.bookstore.daos.ProductDAO;
import br.com.bookstore.infrastructure.FileSaver;
import br.com.bookstore.model.PriceType;
import br.com.bookstore.model.Product;
import br.com.bookstore.validation.ProductValidation;

@Controller
@RequestMapping("/products")
public class ProductsController {

    @Autowired
    private FileSaver fileSaver;

    @Autowired
    private ProductDAO productDAO;

    // responsible for binding the validator with the controller
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.addValidators(new ProductValidation());
    }    

    @RequestMapping(value="/form", method=RequestMethod.GET)
    public ModelAndView form(Product product) {
        ModelAndView modelAndView = new ModelAndView("/products/form");
        modelAndView.addObject("types", PriceType.values());
        return modelAndView;
    }

    @RequestMapping(method=RequestMethod.POST)
    public ModelAndView saveProduct(MultipartFile contentPath, @Valid Product product, BindingResult result, RedirectAttributes redirectAttributes) {
        System.out.println(contentPath.getOriginalFilename());

        if (result.hasErrors()) {
            return form(product);
        }

        String path = fileSaver.write("content-path-folder", contentPath);
        product.setContentPath(path);


        productDAO.save(product);
        redirectAttributes.addFlashAttribute("confirmationMessage", "Product " + product.getTitle() + " added with success.");
        return new ModelAndView("redirect:/products");
    }

    @RequestMapping(method=RequestMethod.GET)
    public ModelAndView list() {
        List<Product> products = productDAO.list();
        ModelAndView modelAndView = new ModelAndView("/products/list");
        modelAndView.addObject("products", products);
        return modelAndView;
    }

}
package br.com.bookstore.infrastructure;

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 write(String baseFolder, MultipartFile file) {

        try {
            String realPath = request.getServletContext().getRealPath("/" + baseFolder);
            String path = realPath + "/" + file.getOriginalFilename();
            file.transferTo(new File(path));
            return baseFolder + "/" + file.getOriginalFilename();
        } catch (IllegalStateException | IOException e) {
            throw new RuntimeException(e);
        }

    }

}
<%@ 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>Products</title>
</head>
<body>

<form:form action="${s:mvcUrl('PC#saveProduct').build() }" method="POST" commandName="product" enctype="multipart/form-data">

    <div>
        <label>Title</label>
        <form:input path="title" />
    </div>
    <div>
        <form:errors path="title"></form:errors>
    </div>


    <div>
        <label>Description</label>
        <form:textarea path="description" rows="10" cols="20" />        
    </div>
    <div>
        <form:errors path="description"></form:errors>
    </div>


    <div>
        <label>Pages:</label>
        <form:input path="pages" />
    </div>
    <div>
        <form:errors path="pages"></form:errors>
    </div>


    <div>
        <label>Published:</label>
        <form:input path="published" />
    </div>
    <div>
        <form:errors path="published"></form:errors>
    </div>



    <c:forEach items="${types}" var="priceType" varStatus="status">
        <div>
            <label>${priceType}</label>
            <form:input path="prices[${status.index}].value" />
            <form:hidden path="prices[${status.index}].type" value="${priceType}" />
        </div>
    </c:forEach>


    <br>
    <div>
        <label>Book Content:</label>
        <input type="file" name="contentPath">
    </div>
    <button type="submit">Submit</button>

</form:form>

</body>
</html>
package br.com.bookstore.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.bookstore.controller.HomeController;
import br.com.bookstore.daos.ProductDAO;
import br.com.bookstore.infrastructure.FileSaver;

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

    @Bean
    public InternalResourceViewResolver internalResourceViewResolver() {
        InternalResourceViewResolver resolve = new InternalResourceViewResolver("/WEB-INF/views/", ".jsp");
        return resolve;
    }

    @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 formatterRegistrar = new DateFormatterRegistrar();
        formatterRegistrar.setFormatter(new DateFormatter("dd/MM/yyyy"));
        formatterRegistrar.registerFormatters(conversionService);
        return conversionService;
    }

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

}
package br.com.bookstore.model;

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 Product {

    // let the database manage the id
    @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;
    private String title;
    private String description;
    private Integer pages;

    private String contentPath;

    @DateTimeFormat
    private Calendar published;

    @ElementCollection
    private List<Price> types;

    @Override
    public String toString() {
        return "Product [title=" + title + ", description=" + description + ", pages=" + pages + "]";
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public Integer getPages() {
        return pages;
    }
    public void setPages(Integer pages) {
        this.pages = pages;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public List<Price> getPrices() {
        return types;
    }
    public void setPrices(List<Price> prices) {
        this.types = prices;
    }
    public Calendar getPublished() {
        return published;
    }
    public void setPublished(Calendar published) {
        this.published = published;
    }
    public String getContentPath() {
        return contentPath;
    }
    public void setContentPath(String contentPath) {
        this.contentPath = contentPath;
    }



}
package br.com.bookstore.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(""));

    }




}

Eu já verifiquei as anotações para injeção de dependência através do Spring, a configuração, estou rodando tanto o eclipse quanto o mysql como root num Ubuntu.

Agradeço qualquer feedback. Grato.

2 respostas
solução!

E ai Davi,blz? Seu controller não está redirecionando porque seu parâmetro result(BindingResult) possui um erro de validação, então seu controller entra no if(result.hasErrors()) retornando novamente para o form. Faça um teste, adicione está linha System.out.println(result.getFieldError()); antes do retorno do if, tente fazer um cadastro e verifique o console.

Acredito que o Spring esteja confundindo o nome o parâmetro MultipartFile com o nome do paramento do Produto(contentPath para ambos). Tente o seguinte:

No fomulário mude o name do input file para sumario.

No controller mude o nome do parâmetro do tipo MultipartFile para sumario.

Espero ter ajudado.

Olá Carlos tudo bem com você também ?

Gostaria de dizer que você tinha razão, eu refatorei um pouco o código da aplicação, tentando deixar esses nomes mais legíveis e manuteníveis.

Chamei o atributo do form de summaryPath, assim como o MultipartFile também de summaryPath para que ocorra o bind corretamente? É isso mesmo? Ou não cabe bind aqui, pois o summaryPath no form é uma String e no Controller é um MultipartFile.

O FileSaver continua passando uma pasta de destino e o MultipartFile (summaryPath).

Na classe Product chamei apenas de summary a String que vai dizer aonde o arquivo foi salvo.

Eu fiz até um diagrama pra entender melhor esses objetos pena que não da pra colocar aqui a imagem.

Deu certo. Tive só um último problema que era a pasta que não existia dentro do diretório de instalação do eclipse, após criada a pasta os arquivos foram salvos lá com sucesso.

Gostaria de lhe agradecer mais uma vez, muito obrigado pela sua ajuda.

Tinha feito um revert no git da implementação dessa funcionalidade mas agora vou fazer um rebase e um merge com a mesma e colocar no github novamente para que outras pessoas possam verificar como ficou o código final se desejarem. https://github.com/DaviGadelhaLeitao/book-store