[Resolvido]Como popular lista de objetos imutáveis com VRaptor?

Boa tarde pessoal!

Na verdade os objetos não são completamente imutáveis.
Existem 2 atributos somente que podem ser alterados… Esse objeto tem um ID que é passado para a view e, da view, esse ID é devolvido com os 2 valores mutáveis preenchidos.

Somente com o ID, eu carrego os outros atributos imutáveis…

Minha dúvida está exatamente em como fazer isso…

Eu tentei criar um construtor para essa classe pegando somente o ID… Mas aí acontece uma exception no instanciador do VRaptor (IOGI)

br.com.caelum.vraptor.http.InvalidParameterException: Exception when trying to instantiate Target(name=objs, type=interface java.util.List)
	at br.com.caelum.vraptor.http.iogi.VRaptorInstantiator.handleException(VRaptorInstantiator.java:95)
	at br.com.caelum.vraptor.http.iogi.VRaptorInstantiator.handleException(VRaptorInstantiator.java:97)
	at br.com.caelum.vraptor.http.iogi.VRaptorInstantiator.instantiate(VRaptorInstantiator.java:87)
	at br.com.caelum.vraptor.http.iogi.VRaptorInstantiator.instantiate(VRaptorInstantiator.java:80)
	at br.com.caelum.vraptor.http.iogi.IogiParametersProvider.instantiateOrAddError(IogiParametersProvider.java:80)
	at br.com.caelum.vraptor.http.iogi.IogiParametersProvider.instantiateParameters(IogiParametersProvider.java:73)
	at br.com.caelum.vraptor.http.iogi.IogiParametersProvider.getParametersFor(IogiParametersProvider.java:63)
	at br.com.caelum.vraptor.interceptor.ParametersInstantiatorInterceptor.getParametersFor(ParametersInstantiatorInterceptor.java:126)
	at br.com.caelum.vraptor.interceptor.ParametersInstantiatorInterceptor.intercept(ParametersInstantiatorInterceptor.java:83)
	at br.com.caelum.vraptor.core.LazyInterceptorHandler.execute(LazyInterceptorHandler.java:59)
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
	at br.com.caelum.vraptor.interceptor.FlashInterceptor.intercept(FlashInterceptor.java:83)
	at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:54)
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
	at br.com.caelum.vraptor.interceptor.ExceptionHandlerInterceptor.intercept(ExceptionHandlerInterceptor.java:71)
	at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:54)
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
	at br.com.caelum.vraptor.interceptor.InstantiateInterceptor.intercept(InstantiateInterceptor.java:48)
	at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:54)
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
	at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:56)
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
	at br.com.caelum.vraptor.interceptor.ResourceLookupInterceptor.intercept(ResourceLookupInterceptor.java:69)
	at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:54)
	at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
	at br.com.caelum.vraptor.core.EnhancedRequestExecution.execute(EnhancedRequestExecution.java:44)
	at br.com.caelum.vraptor.VRaptor$1.insideRequest(VRaptor.java:91)
	at br.com.caelum.vraptor.ioc.spring.SpringProvider.provideForRequest(SpringProvider.java:58)
	at br.com.caelum.vraptor.VRaptor.doFilter(VRaptor.java:88)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:225)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:168)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98)
	at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:927)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407)
	at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1001)
	at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:579)
	at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:312)
	at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
	at java.lang.Thread.run(Thread.java:680)
Caused by: java.lang.NullPointerException
	at br.com.caelum.vraptor.http.iogi.VRaptorInstantiator$VRaptorTypeConverter.setPropertiesAfterConversions(VRaptorInstantiator.java:143)
	at br.com.caelum.vraptor.http.iogi.VRaptorInstantiator$VRaptorTypeConverter.instantiate(VRaptorInstantiator.java:134)
	at br.com.caelum.iogi.MultiInstantiator.instantiate(MultiInstantiator.java:20)
	at br.com.caelum.vraptor.http.iogi.VRaptorInstantiator.instantiate(VRaptorInstantiator.java:85)
	at br.com.caelum.iogi.collections.IndexedListInstantiator.instantiate(IndexedListInstantiator.java:34)
	at br.com.caelum.iogi.collections.ListInstantiator.instantiate(ListInstantiator.java:25)
	at br.com.caelum.iogi.collections.ListInstantiator.instantiate(ListInstantiator.java:10)
	at br.com.caelum.vraptor.http.iogi.NullDecorator.instantiate(NullDecorator.java:25)
	at br.com.caelum.iogi.MultiInstantiator.instantiate(MultiInstantiator.java:20)
	at br.com.caelum.vraptor.http.iogi.VRaptorInstantiator.instantiate(VRaptorInstantiator.java:85)
	... 42 more

Se ficar dificil de entender meu problema, me avisa ai…

Boa Tarde Rafael,

coloca o codigo do seu Controller e seu jsp pra gente da uma olhada

quais são os nomes dos parâmetros que vc tá passando na requisição?

Eu pensei em fazer algo desse tipo na view.

<c:forEach items="${fields}" var="obj" varStatus="status">
	<input type="hidden" name="objs[${status.index}]" value="${obj.id}"><!-- //para pegar o id da classe imutavel. Eu também tentei passando name="objs[${status.index}].id", mas não funcionou. -->
	<label for="${obj.id}" class="label-customer">${obj.label}</label>
<!-- //baseado no ID dessa classe, ela seria populada com os outros valores que estão em memória lá no servidor -->
	<input type="text" id="${obj.id}" name="objs[${status.index}].value" class="component"
		style="width: ${obj.width}px;" maxlength="${obj.length > 0 ? obj.length : ''}" value="${obj.value}">
<!-- //Mas com os atributos mutáveis preenchidos pelo usuário... -->
</c:forEach>

Minha action:

	@Post("/report/save")
	public void saveOrUpdate(List<InputField> objs) {
		logger.debug(objs.toString()); // Só para ver se está vindo certo.
		result.use(Results.status()).accepted(); // Retorna 202 para o AJAX.
	}

Minha classe meio imutavel:

[code]public class InputField implements Comparable<InputField> {

private final String id;
private final int index;
private final String label;
private final String name;
private final int width;
private final SearchField searchField;
private final boolean insertable;
private final boolean updatable;
private final boolean nullable;
private final int length;
private final int precision;
private final int scale;
private final String datePattern;
private final InputType inputType;

private String value; // Mutavel por setter
private String display; // Mutavel por setter

public InputField(String id) { // Eu não quero ter esse construtor aqui, só criei para ver se o erro passava...
	this(id, 0, null, null, 0, null, false, false, false, 0, 0, 0, null,
			null);
}

public InputField(String id, int index, String label, String name,
		int width, SearchField searchField, boolean insertable,
		boolean updatable, boolean nullable, int length, int precision,
		int scale, String datePattern, InputType inputType) {
	// So atribui e verifica integridade
}
// getters

}
[/code]

é isso.
Eu também pensei em criar um converter que recebia a classe preenchida com o ID e os valores mutáveis e ai eu pegaria a classe com o mesmo hash em memória e setaria os dois valores mutáveis…

existe alguma melhor forma de fazer isso?

se vc está usando o iogi, ele consegue preencher os parâmetros do construtor… só passar inputs com names terminando com os nomes dos parâmetros do construtor, como se fossem setters.

Eu não configurei nada. Mas acredito que eu esteja usando IOGI sim… Pois o erro aconteceu no IOGI…

Então eu teria que ter todos esses atributos setados?

    private final int index;  
    private final String label;  
    private final String name;  
    private final int width;  
    private final SearchField searchField;  
    private final boolean insertable;  
    private final boolean updatable;  
    private final boolean nullable;  
    private final int length;  
    private final int precision;  
    private final int scale;  
    private final String datePattern;  
    private final InputType inputType; 

Não tem uma forma de eu setar alguns deles e, no java, eu seto esses, pois eles não fazem parte dessa requisição…

então seu objeto não é imutável :wink: não deveriam estar no construtor

coloque no construtor só os objetos que são obrigatórios… e os outros vc preenche por setters

Hmm… Eu precisava que eles fossem imutáveis…
Portanto coloquei todos na requisição e funcionou bem demais…

Muito obrigado!

vc pode colocar mais construtores, mas isso deixa o modelo um pouco complicado…

não seria interessante vc agrupar alguns desses fields em classes que fazem sentido?

Na verdade todos eles fazem sentido dentro da classe em que estão…
Essa classe (que terá o nome refatorado) contém as configurações de uma coluna no banco de dados.
O que eu queria, na verdade, era minimizar essa requisição em específico…

Se eu criar uma outra classe somente com ID, Display e Value e dessa eu converto para a correta, me parece gambiarra…

Não foi isso que eu sugeri… foi modificar a classe InputField:

public class InputField implements Comparable<InputField> {  
  
    private final String id;  

    // criar uma classe Constraints com isso
    private final int index;
    private final boolean insertable;  
    private final boolean updatable;  
    private final boolean nullable;  

    //classe FormConfig com isso
    private final InputType inputType;  
    private final String label;  
    private final String name;    
    private final SearchField searchField;  

    // classe TypeConfig com isso
    private final int width;
    private final int length;  
    private final int precision;  
    private final int scale;  
    private final String datePattern;  

e um construtor que recebe 3 coisas, ao invés de 15… daí vc conseguiria fazer os construtores sem um dos caras passando o valor pros outros de um jeito mais fácil
mas a InputField continuaria tendo como atributo essas classes novas, sem precisar de conversão pra nada

Não, aquilo lá foi uma das soluções que eu pensei. Mas é muito feia.

Eu entendi essa sua modificação na classe, achei até interessante, mas não entendi como que eu faria para resolver o problema de ter que passar todos os atibutos…
Eu teria que fazer assim: objs[${status.index}].constraints.index para poder setar o index em constraint…

Pela organização, realmente é melhor e eu vou fazer isso.

  • EDIT -
    Eu não entendi essa parte:
    daí vc conseguiria fazer os construtores sem um dos caras passando o valor pros outros de um jeito mais fácil
    mas a InputField continuaria tendo como atributo essas classes novas, sem precisar de conversão pra nada

ao invés de ter um:

public InputField(<5 params>) {
     this(<15 valores default>);
}

vc faria:

public InputField(Constraints constraints) {
     this(constraints, new FormConfig(), new TypeConfig());
}

e em cada uma das classes vc faria um construtor que tem os valores default…

talvez seja melhor nem ser um construtor e sim uma static factory tipo: FormConfig.withDefaultValues();

Então, mas o problema é que não existem valores Default… Pois essa classe é a representação de uma coluna no banco, então o sistema inspeciona o banco e gera essas classes.
Essas classes precisam ser populadas com o que o sistema gerou para elas.

Eu tentei fazer com o construtor somente com o ID da classe para que eu pudesse resgatar o ID e popular os atributos mutáveis… Não deu certo…

Mas fazer isso que eu tentei é uma prática ruim? Teria como eu fazer um converter para ela e no converter eu recebo o ID e pego a classe que contém esse ID e depois eu configuro os 2 atributos mutáveis?

dá sim… cria um converter pra classe e na hora de passar o parâmetro na requisição vc faz sem o .id no final

Então, eu tinha feito isso… Mas ai lançou aquela exception lá em cima. E ele nem chega no converter…

o nullpointer pode ter sido em outra coisa…

tenta fazer um teste:

public void teste(InputField input) {...}
public void teste2(List<InputField> inputs) {...}

e mandar na requisição:

/teste
input = id do maluco  

/teste2
inputs[0] = um id
inputs[1] = outro id

e vê se ainda dá o nullpointer