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

Dúvida - Como criar Converter para Role

Olá. como no curso o usuário foi cadastrado diretamente no banco, resolvi práticar e criar uma página para o cadastro dos usuários.

Consegui efetuar o cadastro em partes, consigo incluir a senha criptografada e o email etc. Mas não consigo incluir as roles.

Na minha tela tenho as checkboxes que marcam as roles para o usuário, mas ao tentar cadastrar tenho o erro:

org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'usuario' on field 'roles': rejected value [ROLE_ADMIN]; codes [typeMismatch.usuario.roles,typeMismatch.roles,typeMismatch.java.util.List,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [usuario.roles,roles]; arguments []; default message [roles]]; default message [Failed to convert property value of type [java.lang.String] to required type [java.util.List] for property 'roles'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [br.com.casadocodigo.models.Role] for property 'roles[0]': no matching editors or conversion strategy found]
    at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:113)
    at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:99)
    at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:161)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:128)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:817)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:731)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:968)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:870)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:648)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:844)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:292)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:121)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:316)
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:126)
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:90)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:114)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:122)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:169)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:48)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:205)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:120)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:120)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:121)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
    at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:522)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1095)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:672)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1502)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1458)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Unknown Source)

Considerando o stracktrace percebo que foi um erro de conversão. Pois então, como criar um Converter?

até tentei o seguinte:

package br.com.casadocodigo.converters;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.converter.Converter;

import br.com.casadocodigo.models.Role;
import br.com.casadocodigo.dao.RoleDAO;

public class RoleConverter implements Converter<String, Role>{

    @Autowired
    private RoleDAO dao;

    @Override
    public Role convert(String roles) {
        if (roles == null || roles.isEmpty()){
            return null;
        }
        System.out.println(roles);
        Role role = dao.buscar(roles);
        return role;
    }
}

e adicionei no AppWebConfi:

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new RoleConverter());
    }

Mas continuo sem conseguir efetuar o cadastro do usuário e sua(s) Role(s).

14 respostas

Olá Vinícius,

Também fiz uma tela cadastro no meu sistema mas não cheguei a criar um converter. Funcionou normalmente só fazendo as ligações na jsp mesmo.. ficou assim:

as roles que eu queria no sistema eu deixei previamente cadastradas na mão mesmo..

no controller que chama o cadastro coloquei pra adicionar as roles no model and view

@RequestMapping(value="/cadastro", method=RequestMethod.GET)
    public ModelAndView form(Usuario usuario){
        ModelAndView view = new ModelAndView("usuario/cadastro_usuario");
        view.addObject("roles", roleDao.getRoles());
        return view;
    }

ai na JSP eu só li esse objeto com o forEach

<c:forEach items="${roles }" var="role">
                            <label for="${role }" >
                                <form:checkbox id="${role }" path="roles" value="${role.role }"/>
                                <span>${role.nome }</span>
                            </label>
                        </c:forEach>

No modelo Role eu coloquei o atributo nome pra quando for exibir na JSP ficar mais bonito. No modelo Usuario eu anotei a list de role com @ManyToMany(fetch=FetchType.EAGER) para os usuários poderem usar a mesma role.

Tenta ai...

Obrigado pela ajuda Fillipe, mas, fiz aqui como você me passou e continuo com o mesmo erro.

Oi Vinicius,

Coloca aqui o código da sua classe usuário, como ela ficou no final. O código do DAO pra salvar o usuário também pode ajudar. Ah, e o controller com sua JSP também rsrs.. enfim, tudo ligado ao cadastro.

Se preferir colocar o código no github, eu olho por lá já.

Mas só pra adiantar, não é preciso converter realmente para que tudo funcione, e desde já parabéns por ir além do que ensinamos no curso.

Abraço

Oi paulo, tá aí:

https://github.com/vinicius-rocha/alura-springMVC-I

Obrigado pela ajuda.

Fillipe, por favor.

Ainda continuo com o mesmo problema, poderia ver os códigos completos das classes relacionadas ao cadastro do usuário? Model, Controller, Dao e JSP.

Desde já agradeço.

Oi Vinícius, você pode linkar para a jsp em si? Sem olhar a página, meu chute é que você não usou a sintaxe dos [indice] para associar as roles.

Abraço!

solução!

Vinicius, como o Alberto falou, o problema é na JSP:

<c:forEach items="${roles}" var="role" varStatus="status">
<label for="${role}"> 
<form:checkbox id="${role}" path="roles[${status.index}]" value="${role.nome}" /> <span>${role.nome}</span>
</label>
</c:forEach>

Veja se isso ajuda. (não testei)

Abraço

Oi Paulo e Alberto, obrigado pelas respostas.

Paulo, com o código que me passou continua dando status 400.

testei incluindo no path ".nome"

<c:forEach items="${roles}" var="role" varStatus="status">
    <form:checkbox path="roles[${status.index}].nome"
        value="${role.nome}" />
    <span>${role}</span>
</c:forEach>

Isso resolveu em partes.

O que acontece é: Eu tenho duas roles, ADMIN, USER.

se eu seleciono as duas, blz!

##resultado do System.out.println(usuario.getRoles()); ##
[ADMIN, USER]##
Hibernate: insert into Usuario (dataHoraCadastro, email, nome, recuperadorSenha_id, senha) values (?, ?, ?, ?, ?)
Hibernate: insert into Usuario_Role (Usuario_id, roles_nome) values (?, ?)
Hibernate: insert into Usuario_Role (Usuario_id, roles_nome) values (?, ?)

se eu seleciono apenas uma, de acordo com o sysou, o 2º elemento está vindo null, causando o erro:

[ADMIN, null]
Hibernate: insert into Usuario (dataHoraCadastro, email, nome, recuperadorSenha_id, senha) values (?, ?, ?, ?, ?)
Hibernate: insert into Usuario_Role (Usuario_id, roles_nome) values (?, ?)
Hibernate: insert into Usuario_Role (Usuario_id, roles_nome) values (?, ?)

[Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: br.com.gaviadespachante.modelo.Role; nested exception is java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: br.com.gaviadespachante.modelo.Role] with root cause
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: br.com.casadocodigo.modelo.Role
`

Oi Vinicius,

Você pode executar uma limpeza na lista para remover os elemento nulos com Lambda do Java 8.

usuario.getRoles().stream().filter(s -> s != null).collect(Collectors.toList())

No exemplo acima, você só tem que receber de volta a lista alterada e atribuí-la ao usuario.setRoles(novasRoles).

O que acha? Ou pode ver se usando <select multiple...> o resultado é o mesmo que com checkbox.

Abraço

Paulo, deu certo! Obrigado pela força.

Não tenho muita experiência ainda, estou com uma dúvida, relacionada a coesão e baixo acoplamento que vi em outros cursos, mas ainda não sei aplicar tão bem, dúvida:

Seria melhor eu deixar esse Lambda no Controller ou nos métodos getter ou setter da List<Role>?

como abaixo:

public void setRoles(List<Role> roles) {
        this.roles = roles.stream().filter(role -> role.getNome() != null).collect(Collectors.toList());
    }

Pesquisei e tenti utilizar o <select multiple...>, mas não consegui, tentei algumas formas, a última segue abaixo, me retorna o status 400.

 <form:select path="roles" multiple="true">
     <form:options items="${roles}" itemValue="nome" itemLabel="nome"/>
 </form:select>

estou com certa dificuldade ainda em entender e usar essa relação @ManyToMany diretamente na jsp. Quando, como e onde usar o índice [status.index].

Mas deu certo, isso que importa, mas se tiver algum material bom e confiável para que eu possa aprender mais sobre isso agradeço.

Oi Vinícius,

Seria melhor eu deixar esse Lambda no Controller ou nos métodos getter ou setter da List<Role>?

Quando falamos de baixo acoplamento e alta coesão, a ideia geral é que o as classes não tomem responsabilidade de outras classes (coesão) e que sua implementação não precise ser conhecida para fazer uso dessas classes (acoplamento). Em resumo seria isso no problema atual.

Para seu problema eu sugiro deixar no controller mesmo, pois isso é uma "falha" no envio dos dados. Não uma falha, mas um comportamento normal do HTML que precisamos corrigir no Controller. Passar isso para a Entidade pode parecer que o problema da tela está vasando para seu modelo o que pode quebrar a coesão e encapsulamento.

Pesquisei e tenti utilizar o <select multiple...>, mas não consegui, tentei algumas formas, a última segue abaixo, me retorna o status 400.

O <select multiple funcionaria bem parecido com o checkbox. Mas já que conseguiu com checkbox, deixa assim por enquanto. Você pode evoluir depois.

Sobre o status.index, você usa quando precisa de um valor sendo incrementado pelo <c:forEach />. Geralmente usamos o ID dos objetos para isso, mas nem sempre é possível, como no caso do uso do checkbox. Ele não está ligado diretamente ao @ManyToMany, um não depende do outro. No caso do cadastro que você está fazendo, casou dos dois terem essa relação. Mas o @ManyToMany é para dizer ao JPA que: 1 Usuário possui N Roles e que 1 Role pode estar N Usuários. Assim, o JPA vai criar uma tabela no banco de dados para ligar os dois. Resumindo: @ManyToMany é para o JPA se relacionar com o Banco de Dados.

Foram muitos assuntos misturados rsrs .. mas espero que tenha dado para entender algo. Se ainda tiver dúvidas, pode mandar que vamos tentando resolver. :)

Abraço,

Realmente Paulo,

muito obrigado pelos ensinamentos. Realmente muitos assuntos misturados, mas isso é culpa do alura e de vocês instrutores, quanto mais conhecimento, mais conhecimento queremos.

Só por curiosidade, estava aqui nos testes loucos, um pouco inconformado com "falha" como você mesmo disse do checkbox.

incluí um construtor como abaixo, além do default:

public Role(String nome) {
        this.nome = nome;
    }

e na jsp

<form:checkboxes items="${roles}" path="roles" itemValue="nome"/>

dessa forma, ao selecionar apenas 1 checkbox, ele envia apenas 1 elemento para List<Role>.

Bom, chega por esse tópico, já rendeu muito! haha

Obrigado.

** E excluí os trechos de código lambda.

"Realmente muitos assuntos misturados, mas isso é culpa do alura e de vocês instrutores, quanto mais conhecimento, mais conhecimento queremos."

hahahaha.. maravilha. Mas isso é bom mesmo. E muito boa a ideia de aplicar o conhecimento.

Como disse no início da dúvida, parabéns por ter ido além e fazer o cadastro de Usuários com as Roles.

Abraço