VRaptor + Injeção no Field

33 respostas
R

Achei alguns posts aqui no GUJ e na lista de discussão do VRaptor.

http://groups.google.com/group/caelum-vraptor/browse_thread/thread/630a64caccf465ea
http://www.guj.com.br/java/228622-vraptor-setar-valores-no-controller
http://www.guj.com.br/java/218207-ioc-vraptor

Mas, não funcionou aqui. Não sei se fiz algo de errado. Tenho o @Component anotado.

private ClienteDao clientes;

@Autowired
public void setClientes(ClienteDao clientes)
{
this.clientes = clientes;
}

Preciso fazer alguma outra configuração? Ou o @Component anotado já basta?

P.S: Não posso fazer a injeção no construtor. Porque essas classes serão serviços do BlazeDS (comunicação com um cliente em Flex) e o BlazeDS exige que a classe tenha ao menos um construtor nulo. Ou seja, a injeção por construtor não funcionaria.

33 Respostas

Lucas_Cavalcanti

a injeção só vai funcionar se foi o VRaptor quem instanciou a classe, se foi o BlaseDS não rola

R

E tem alguma outra maneira (qualquer maneira) de injetar um @Component? Sem ser pelo construtor, nesse caso?
Ou nesse caso não posso usar o VRaptor?

Lucas_Cavalcanti

é o blaseDS que dá new nessa classe?

R

Vou verificar…

R

Lucas,

Ainda não consegui montar o workspace para debugar o BlazeDS para verificar, com certeza, se é ele quem cria a classe.
Mas, eu acredito que seja ele.

Para adiantar:

-Se BlazeDS não é quem cria a classe = VRaptor deveria injetar corretamente
-Se BlazeDS é quem cria a classe = VRaptor não conseguirá injetar

Certo?

Então, pergunto: Se o VRaptor não conseguirá injetar. Tenho como fazer essa injeção do @Component “manualmente” (sem depender do VRaptor). Usando um dos providers como Spring, Guice ou Pico?

Lucas_Cavalcanti

tenta fazer com que o flex mande uma request pra sua aplicação VRaptor. Senão o único jeito é fazendo um static lookup (que é feio pra cacete).

R

Então… o que você quer dizer em fazer um request para a aplicação VRaptor?

Mais informações:

O projeto Java estava rodando com VRaptor e estava disponibilizando os serviços por HTTP (@Resource). Então, se eu chamar no Flex os dados por HTTP irá fazer o request diretamente no VRaptor. E funcionará tanto a injeção por construção, como também acho que funcionará no setter.

No entanto, a forma mais usada e rápida de comunicação no Flex é por RemoteObject. E quem faz a comunicação entre o cliente e o servidor é o BlazeDS. Então, no servidor estou com o VRaptor, onde tem serviços sendo disponibilizados como HTTP. Mas, gostaria de disponibilizar o acesso a alguns dados por RemoteObject.

Ele tem um arquivo de configuração xml onde atribuo uma id para cada serviço disponibilizado e informo o package onde está o serviço. Depois, no Flex eu só passo a localização do servidor e o nome do serviço que será chamado. O Flex não tem nenhuma ligação com o servidor Java. Tudo passa pelo BlazeDS.

Portanto, creio, que o BlazeDS receba essa mensagem com o id do serviço que ele precisa chamar. E ela fica responsável por criar a classe. (senão não teria necessidade de informar o package…)

Não entendi ainda, por que o BlazeDS como responsável por criar a classe, a injeção das dependencias não funciona? Com nenhum framework ela funcionaria?

Lucas_Cavalcanti

a injeção não funciona pq quando o BlaseDS instancia a classe, ela não é gerenciada pelo VRaptor.

vc consegue pegar a instancia dessa classe de outro lugar? ou ela executa alguma coisa sem nem passar pelo vraptor?

R

Ta aqui o stacktrace de uma Exception: (me ajudou a entender melhor o ciclo da requisição)

O request está passando pelo VRaptor!
O BlazeDS tem uma servlet que fica, normalmente, em: http://servidor/nomedoprojeto/messagebroker/amf

Essa servlet é uma fachada para todos os acessos, ou seja, TODA requisição realizada pelo cliente Flex (através do AMF) o servidor Java recebe através dessa servlet. Portanto, passa pelo VRaptor.

A requisição também passa pelo provider:
at br.com.caelum.vraptor.ioc.spring.SpringProvider.provideForRequest(SpringProvider.java:58)

No entanto, nesse momento que a classe passa pelo provider é apenas uma mensagem que contém a localização do serviço, parametros, e outras informações da requisição. (Não existe a classe criada!)

Depois, por dentro do BlazeDS é criada a classe que executa a lógica e retorna o resultado em outra mensagem.

Vou verificar se essa classe responsável por criar o serviço não tem uma interface que eu possa implementar para alterar o comportamento da criação. Se eu conseguisse, teria uma maneira do VRaptor injetar daí?

Stacktrace:

<blockquote>15:42:35,133 DEBUG [VRaptor             ] VRaptor received a new request

15:42:35,145 DEBUG [ToInstantiateInterceptorHandler] Invoking interceptor ResourceLookupInterceptor

15:42:35,146 DEBUG [DefaultResourceTranslator] trying to access /messagebroker/amf

15:42:35,148 DEBUG [VRaptor             ] VRaptor ended the request

15:42:35,195 DEBUG [VRaptor             ] VRaptor received a new request

15:42:35,204 DEBUG [ToInstantiateInterceptorHandler] Invoking interceptor ResourceLookupInterceptor

15:42:35,205 DEBUG [DefaultResourceTranslator] trying to access /messagebroker/amf

[BlazeDS]Unable to create a new instance of type br.com.cauirs.tipos.Quantidade.

flex.messaging.MessageException: Unable to create a new instance of type br.com.cauirs.tipos.Quantidade. Types cannot be instantiated without a public, no arguments constructor.

at flex.messaging.util.ClassUtil.createDefaultInstance(ClassUtil.java:161)

at flex.messaging.io.amf.AbstractAmfInput.createObjectInstance(AbstractAmfInput.java:179)

at flex.messaging.io.amf.Amf3Input.readScriptObject(Amf3Input.java:409)

at flex.messaging.io.amf.Amf3Input.readObjectValue(Amf3Input.java:152)

at flex.messaging.io.amf.Amf3Input.readObject(Amf3Input.java:130)

at flex.messaging.io.amf.Amf3Input.readScriptObject(Amf3Input.java:437)

at flex.messaging.io.amf.Amf3Input.readObjectValue(Amf3Input.java:152)

at flex.messaging.io.amf.Amf3Input.readObject(Amf3Input.java:130)

at flex.messaging.io.amf.Amf3Input.readScriptObject(Amf3Input.java:437)

at flex.messaging.io.amf.Amf3Input.readObjectValue(Amf3Input.java:152)

at flex.messaging.io.amf.Amf3Input.readObject(Amf3Input.java:130)

at flex.messaging.io.amf.Amf3Input.readArray(Amf3Input.java:358)

at flex.messaging.io.amf.Amf3Input.readObjectValue(Amf3Input.java:156)

at flex.messaging.io.amf.Amf3Input.readObject(Amf3Input.java:130)

at flex.messaging.io.amf.Amf3Input.readScriptObject(Amf3Input.java:437)

at flex.messaging.io.amf.Amf3Input.readObjectValue(Amf3Input.java:152)

at flex.messaging.io.amf.Amf3Input.readObject(Amf3Input.java:130)

at flex.messaging.io.amf.Amf0Input.readObjectValue(Amf0Input.java:123)

at flex.messaging.io.amf.Amf0Input.readArrayValue(Amf0Input.java:359)

at flex.messaging.io.amf.Amf0Input.readObjectValue(Amf0Input.java:127)

at flex.messaging.io.amf.Amf0Input.readObject(Amf0Input.java:94)

at flex.messaging.io.amf.AmfMessageDeserializer.readObject(AmfMessageDeserializer.java:227)

at flex.messaging.io.amf.AmfMessageDeserializer.readBody(AmfMessageDeserializer.java:206)

at flex.messaging.io.amf.AmfMessageDeserializer.readMessage(AmfMessageDeserializer.java:126)

at flex.messaging.endpoints.amf.SerializationFilter.invoke(SerializationFilter.java:145)

at flex.messaging.endpoints.BaseHTTPEndpoint.service(BaseHTTPEndpoint.java:291)

at flex.messaging.MessageBrokerServlet.service(MessageBrokerServlet.java:353)

at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)

at br.com.caelum.vraptor.resource.DefaultResourceNotFoundHandler.couldntFind(DefaultResourceNotFoundHandler.java:41)

at br.com.caelum.vraptor.interceptor.ResourceLookupInterceptor.intercept(ResourceLookupInterceptor.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.core.EnhancedRequestExecution.execute(EnhancedRequestExecution.java:23)

at br.com.caelum.vraptor.VRaptor$1.insideRequest(VRaptor.java:92)

at br.com.caelum.vraptor.ioc.spring.SpringProvider.provideForRequest(SpringProvider.java:58)

at br.com.caelum.vraptor.VRaptor.doFilter(VRaptor.java:89)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)

at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)

at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)

at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)

at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)

at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)

at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298)

at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:857)

at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:588)

at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)

at java.lang.Thread.run(Thread.java:636)</blockquote>
R

Confirmado. Tenho como modificar o modo que o BlazeDS cria o objeto.

Ao passar pelo servlet é um objeto do tipo RemotingMessage com as informações da requisição. Ai utiliza um Adapter para fazer a criação do objeto. Por padrão, é usado o JavaAdapter. Mas, posso criar qualquer um…

Então, é o BlazeDS mesmo quem cria o objeto. Porém, utilizando um Adapter personalizado a classe estará no meu projeto posso dizer o que fazer com o objeto criado.

Tenho como “ativar” essa injeção de dependências após a criação do objeto?

Lucas_Cavalcanti

tem como mudar o modo que o BlaseDS instancia as classes de serviço, então dá pra fazer o VRaptor cuidar disso.

O David Paniz e o Erich Egert aqui da Caelum estão trabalhando nessa integração, assim que estiver estável eu te dou um toque.

Abraços

R

Opa… valeu

davidpaniz

Acabamos disponibilizar o plugin para fazer a chamada do seu controller pelo BlazeDS.
Você pode baixar o jar (vraptor-flex.jar) na área de downloads do VRaptor no github: https://github.com/caelum/vraptor/

Na página do projeto (https://github.com/caelum/vraptor/tree/master/vraptor-plugin-flex) tem uma breve explicação sobre a configuração do plugin, mas já aproveitando a resposta, para a chamada funcionar você precisa registrar a factory do VRaptor no seu service-config.xml

<factories>
  <factory id="vraptor" class="br.com.caelum.vraptor.flex.VRaptorServiceFactory" />
</factories>

e na hora de registrar seus destinations, você tem que passar o nome canonico do seu controller e a fabrica do vraptor:

<destination id="myVRaptorController">
  <properties>
    <factory>vraptor</factory>
    <source>br.com.caelum.vraptor.example.MyController</source>
  </properties>
</destination>

Nesta versão já estamos suportando interceptors, mas como nem todo interceptor faz sentido tanto para o flex quanto para o html comum, criamos uma nova anotação @FlexIntercepts que deve anotar um classe que implementa a mesma interface Interceptor. (Uma classe que implementa Interceptor PODE usar a @Intercepts e @FlexIntercepts ao mesmo tempo sem problemas)

R

David parabéns ficou muito bom :smiley:
Agora é possível até injeção de dependências pelo construtor. Ficou melhor do que eu estava esperando!

Só uma observação: usando o jar que está no github está dando um erro de NullPointerException, precisei copiar as classes para dentro do meu projeto.

Lucas_Cavalcanti

nullpointer aonde?

C

Acredito ter seguido corretamente as instruções porém durante o deploy recebo isso:

C

Mudei a factory de:

<factories> <factory id="vraptor" class="br.com.caelum.vraptor.flex.VRaptorServiceFactory" /> </factories>

para:

<factories> <factory id="vraptor" class="br.com.caelum.vraptor.flex.blazeds.VRaptorServiceFactory" /> </factories>

e parece que mudou o erro para:

exception

javax.servlet.ServletException: java.lang.NoClassDefFoundError: org/apache/commons/httpclient/UsernamePasswordCredentials
	flex.messaging.MessageBrokerServlet.init(MessageBrokerServlet.java:184)
	org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:181)
	org.jboss.modcluster.catalina.CatalinaContext$RequestListenerValve.event(CatalinaContext.java:285)
	org.jboss.modcluster.catalina.CatalinaContext$RequestListenerValve.invoke(CatalinaContext.java:261)
	org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:88)
	org.jboss.web.tomcat.security.SecurityContextEstablishmentValve.invoke(SecurityContextEstablishmentValve.java:100)
	org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
	org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:158)
	org.jboss.web.tomcat.service.request.ActiveRequestResponseCacheValve.invoke(ActiveRequestResponseCacheValve.java:53)
	org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:362)
	org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:877)
	org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:654)
	org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:951)
	java.lang.Thread.run(Unknown Source)

root cause

java.lang.NoClassDefFoundError: org/apache/commons/httpclient/UsernamePasswordCredentials
	java.lang.Class.forName0(Native Method)
	java.lang.Class.forName(Unknown Source)
	flex.messaging.util.ClassUtil.createClass(ClassUtil.java:65)
	flex.messaging.Destination.createAdapter(Destination.java:358)
	flex.messaging.config.MessagingConfiguration.createAdapter(MessagingConfiguration.java:431)
	flex.messaging.config.MessagingConfiguration.createDestination(MessagingConfiguration.java:423)
	flex.messaging.config.MessagingConfiguration.createServices(MessagingConfiguration.java:391)
	flex.messaging.config.MessagingConfiguration.configureBroker(MessagingConfiguration.java:117)
	flex.messaging.MessageBrokerServlet.init(MessageBrokerServlet.java:132)
	org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:181)
	org.jboss.modcluster.catalina.CatalinaContext$RequestListenerValve.event(CatalinaContext.java:285)
	org.jboss.modcluster.catalina.CatalinaContext$RequestListenerValve.invoke(CatalinaContext.java:261)
	org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:88)
	org.jboss.web.tomcat.security.SecurityContextEstablishmentValve.invoke(SecurityContextEstablishmentValve.java:100)
	org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
	org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:158)
	org.jboss.web.tomcat.service.request.ActiveRequestResponseCacheValve.invoke(ActiveRequestResponseCacheValve.java:53)
	org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:362)
	org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:877)
	org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:654)
	org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:951)
	java.lang.Thread.run(Unknown Source)

root cause

java.lang.ClassNotFoundException: org.apache.commons.httpclient.UsernamePasswordCredentials from BaseClassLoader@7428a612{vfs:///D:/programas/jboss-6.0.0.Final/server/default/deploy/AgileSuitePasmania.war}
	org.jboss.classloader.spi.base.BaseClassLoader.loadClass(BaseClassLoader.java:480)
	java.lang.ClassLoader.loadClass(Unknown Source)
	java.lang.Class.forName0(Native Method)
	java.lang.Class.forName(Unknown Source)
	flex.messaging.util.ClassUtil.createClass(ClassUtil.java:65)
	flex.messaging.Destination.createAdapter(Destination.java:358)
	flex.messaging.config.MessagingConfiguration.createAdapter(MessagingConfiguration.java:431)
	flex.messaging.config.MessagingConfiguration.createDestination(MessagingConfiguration.java:423)
	flex.messaging.config.MessagingConfiguration.createServices(MessagingConfiguration.java:391)
	flex.messaging.config.MessagingConfiguration.configureBroker(MessagingConfiguration.java:117)
	flex.messaging.MessageBrokerServlet.init(MessageBrokerServlet.java:132)
	org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:181)
	org.jboss.modcluster.catalina.CatalinaContext$RequestListenerValve.event(CatalinaContext.java:285)
	org.jboss.modcluster.catalina.CatalinaContext$RequestListenerValve.invoke(CatalinaContext.java:261)
	org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:88)
	org.jboss.web.tomcat.security.SecurityContextEstablishmentValve.invoke(SecurityContextEstablishmentValve.java:100)
	org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
	org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:158)
	org.jboss.web.tomcat.service.request.ActiveRequestResponseCacheValve.invoke(ActiveRequestResponseCacheValve.java:53)
	org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:362)
	org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:877)
	org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:654)
	org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:951)
	java.lang.Thread.run(Unknown Source)
Lucas_Cavalcanti

class not found… tá faltando o jar do commons-httpclient

C

já fiz isso e dai ta dando:

Lucas_Cavalcanti

qual versão vc colocou?

C

3.1

davidpaniz

Tem certeza que você está com todas as dependencias do BlazeDS? Parece que está dando error dentro dele, antes mesmo de chegar no VRaptor.
De uma olhada nesse projeto exemplo pra ver o classpath e as configurações:

C

eu peguei todo o classpath de outros projetos BlazeDS que tenho funcionando e coloquei no classpath desse projeto VRaptor que também esta funcionando.

davidpaniz

Isso ainda está com cara de problema na versão do commons-httpclient. Em qual servidor você está testando? Tente em outro container só pra ver o que acontece, e/ou tente subir o meu exemplo que está funcionando no seu servidor pra ver se você tem o mesmo problema. Aparentemente é um problema clássico de “classpath hell”

C

peguei todas libs do teu projeto agora e coloquei no meu projeto… dai o Blaze subiu… mas… agora meu projeto antigo que estava rolando ta dando isso ao tentar submeter um form:

Suspeito que seja por causa dessa configuração que eu tinha antes no VRaptor, quando o projeto funcionava:

<context-param> <param-name>br.com.caelum.vraptor.packages</param-name> <param-value>br.com.rokas.agilesuite</param-value> </context-param>

… e agora pra funcionar o blaze esta como:

<context-param> <param-name>br.com.caelum.vraptor.packages</param-name> <param-value>br.com.caelum.vraptor.flex</param-value> </context-param>

C

Acho que estava faltando alguma lib que tinha no meu projeto e não tinha no teu… o que fiz:

voltei meu web.xml para:

<context-param> <param-name>br.com.caelum.vraptor.packages</param-name> <param-value>br.com.rokas.agilesuite</param-value> </context-param>

E copiei todas as libs que ja tinha sobre as tuas.

Com isso tudo funcionando, o blaze e o projeto antigo com vraptor.

Agora vou testar o flex acessando o Blaze.

Mas muito obrigado pela atenção, e estou a disposição no que puder ajudar.

ABraços!

davidpaniz

Quando você tem dependências em dois pacotes diferente você tem que usar só um context-param com os pacotes separados por vírgula igual na documentação: http://vraptor.caelum.com.br/documentacao/componentes-utilitarios-opcionais/

no seu caso ficaria:

<context-param>  
  <param-name>br.com.caelum.vraptor.packages</param-name>  
  <param-value>
    br.com.rokas.agilesuite,
    br.com.caelum.vraptor.flex
  </param-value>  
</context-param>
C

pois é, isso é engraçado, pois deixei apenas br.com.rokas.agilesuite que é o pacote base da minha aplicação e está tudo funcionando…

C

olha só, meu controller de login, funcionando na aplicação, tem o seguinte método:

@Path("/login") @Post @NotLoggedIn public void login(String login, String senha) throws Exception {

Porém pelo blaze ele parece que não consegue achar… seria por causa das anotations?

Todos exemplos que vi, os controllers não possuiam anotations?

davidpaniz

Funciona mesmo com as annotations. O que exatamente está acontecendo?

C

Estava faltando isso que já tinhamos falado:

<context-param> <param-name>br.com.caelum.vraptor.packages</param-name> <param-value> br.com.rokas.agilesuite, br.com.caelum.vraptor.flex </param-value> </context-param>

Além disso verifiquei meu controller de login está cheio de results e estes não são bem recebidos pelo Swiz, pelo que verifiquei.

Então fiz um CTRL+C e CTRL+V no loginController mudando os retornos apenas para ter o “return” e sem results.

Mas parece que agora estabilizou o teste de conceito.

Ficou assim:

Adobe AIR + Swiz + BlazeDS + Plugin Flex + Vraptor

C

Veja que sem sentido isso.

estou usando o plugin flex com vraptor pro Login e pra um grid com sucesso.

Mas quando tento fazer um insert, ele não da erro, me retorna o ID, mas não insere nada.

Este seria o código da chamada para a DAO:
UnidadeMedida unidadeMedida = new UnidadeMedida();
			unidadeMedida.setDescricao(unidadeMedidaBean.getDescricao());
			unidadeMedida.setSigla(unidadeMedidaBean.getSigla());
			unidadeMedida.setStatus("1");
			unidadeMedida.setUsuarioMaster(usuarioMasterDAO.get(new Long(1)));

			System.out.println("** ADICIONA DAO...");
			dao.save(unidadeMedida);

E este seria o código da DAO:

public Long save(UnidadeMedida unidadeMedida) {
		System.out.println("** ADICIONA DAO 1...");
		Session session = super.getSession();

		Long saida = (Long) session.save(unidadeMedida);
		System.out.println("** FIM DO ADICIONA DAO 1..." + saida);
		return saida;
	}

alguma dica?

C

Resolvido.

Acrescentei isso no hybernate:

Criado 22 de março de 2011
Ultima resposta 13 de mar. de 2012
Respostas 33
Participantes 4