Java + Flex = Aplicação bonita e rápida e backend confiável!

Vocês provavelmente ja ouviram falar do Adobe Flex.
O Flex é um SDK para desenvolver aplicações Ricas para Internet, estas aplicações irão rodar no Flash Player que ja esta presente em quase todos os browsers que acessam a internet hoje.
Uma aplicação Flex, gera um arquivo SWF e este arquivo se comunica com um servidor, e este servidor pode ser um Adobe Lifecycle Data Server, mas vocês também podem utilizar Java e Open Source :D

Estes 2/3 exemplos rodam sem nenhum software pago, as únicas coisas necessárias são:
Flex SDK - O Flex SDK é gratuito e a versão 3 vai ser Open Source também.
Um Servlet Container - Eu estou utilizando o Tomcat
– Para o primeiro exemplo é só isto :D
E para os outros dois exemplos, também será necessário baixar os seguintes softwares:
OpenAMF - Uma implementação do protocolo AMF0 (Flex Remoting) em Java
RemoteObjectAMF0 - Uma implementação da tag RemoteObject que suporta a versão 0 do AMF (OpenAMF ainda não suporta a versão 3 e o Flex 2 por padrão utiliza a versão 3)

Então, comecemos criando uma aplicação Web Java na sua IDE favorita, crie um servlet de nome TestServlet, eu vou utilizar JAXB para renderizar XML, mas vocês podem utilizar qualquer outra coisa, se você quiser utilizar JAXB também, o schema que eu utilizei para este exemplo esta abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.example.com/example" xmlns:tns="http://www.example.com/example" elementFormDefault="qualified">
    <element name="TestSVo" type="tns:TestList"></element>
        <complexType name="TestSVoType">
    	<attribute name="id" type="int"></attribute>
    	<attribute name="name" type="string"></attribute>
    	<attribute name="other" type="string"></attribute>
    </complexType>
    <complexType name="TestList">
    	<sequence minOccurs="1" maxOccurs="unbounded">
    		<element name="all" type="tns:TestSVoType"></element>
    	</sequence>
    </complexType>
</schema>

Este Schema é para um XML parecido com este:

1
2
3
4
5
<TestSVo>
<all id="0" name="foo" other="bar"/>
<all id="1" name="foo1" other="bar1"/>
...
</TestSVo>

Agora é preciso gerar os stubs para o JaxB ou utilize qualquer outra ferramenta que gere um XML parecido com este.
Generate the stubs for the XML generation using JaxB or use any other tool to generate a XML with this schema.

Como em qualquer outra aplicação Java WEB, eu vou começar poluindo meu código com XML (o web.xml neste caso):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd ">
	<servlet>
		<servlet-name>testServlet</servlet-name>
		<servlet-class>....servlet.TestServlet</servlet-class>
	</servlet>
	<servlet>
		<servlet-name>AdvancedGateway</servlet-name>
		<servlet-class>org.openamf.AdvancedGateway</servlet-class>
		<init-param>
			<description>
				Location of the OpenAMF config file.
			</description>
			<param-name>OPENAMF_CONFIG</param-name>
			<param-value>/WEB-INF/openamf-config.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>AdvancedGateway</servlet-name>
		<url-pattern>/gateway2</url-pattern>
	</servlet-mapping>
	<servlet-mapping>
		<servlet-name>testServlet</servlet-name>
		<url-pattern>/TestServlet</url-pattern>
	</servlet-mapping>
	<session-config>
		<session-timeout>30</session-timeout>
	</session-config>
	<welcome-file-list>
		<welcome-file>teste.html</welcome-file>
	</welcome-file-list>
</web-app>

Para o primeiro exemplo, apenas o TestServlet é utilizado, para os outros o AdvancedGateway que será utilizado …

Agora vamos começar codificando o Servlet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import java.io.IOException;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
 
/**
 * Servlet implementation class for Servlet: TestServlet
 * 
 */
public class TestServlet extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet
{
	static final long serialVersionUID = 1L;
	private JAXBContext jc;
	private Marshaller marc;
	private Unmarshaller unmarc;
	private ObjectFactory factory;
 
	public TestServlet()
	{
		super();
		factory = new ObjectFactory();
		try {
			jc = JAXBContext.newInstance("....servlet");
			marc = jc.createMarshaller();
			unmarc = jc.createUnmarshaller();
		} catch (JAXBException e) {
			e.printStackTrace();
		}
 
	}
 
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
	{
		float v = Float.parseFloat(request.getParameter("v"));
		float v1 = Float.parseFloat(request.getParameter("v1"));
		response.getWriter().print(v * v1);
	}
 
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
		IOException
	{
		System.out.println("List called");
		TestList res = new TestList();
		for(int i=0;i<40;i++){
			res.getAll().add(new TestSVoType(i,"Téste ã " + i));
		}
		JAXBElement<TestList> elem = factory.createTestSVo(res);
		System.out.println(elem.toString());
		try {
			marc.marshal(elem, response.getWriter());
		} catch (JAXBException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

O método doGet deste servlet é uma calculadora, ele apenas multiplica dois parâmetros recebidos e imprime o resultado.
Já o doPost imprime um XML como o mostrado anteriormente, o conteúdo é praticamente estático, mas isto é só um exemplo :D

Agora vamos começar a brincar com Flex …
Crie um arquivo de nome xmltest.mxml na pasta raiz da sua aplicação WEB.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
	 layout="vertical"	backgroundColor="#F4F4F4">
	 <mx:HTTPService id="calc" method="GET" url="http://localhost:8080/teste/TestServlet" >
	 </mx:HTTPService>
	 <mx:HTTPService id="list" method="POST" url="http://localhost:8080/teste/TestServlet" >
	 </mx:HTTPService>
	<mx:Script>
		<![CDATA[
			import mx.controls.DataGrid;
            import mx.controls.Alert;
        ]]>
	</mx:Script>
	<mx:Panel width="80%" height="531" title="Teste">
		<mx:Label width="100%" color="blue"
			text="Type two numbers and press calculate." />
		<mx:TextInput id="v1" text="3" />
		<mx:TextInput id="v2" text="5" />
		<mx:Label id="lbl"  text="{calc.lastResult}"/>
		<mx:Button label="Calculate" click="calc.send({v:v1.text,v1:v2.text});" />
		<mx:DataGrid dataProvider="{list.lastResult.TestSVo.all}" width="100%" height="195" change="Alert.show(DataGrid(event.currentTarget).selectedItem.other)">
			<mx:columns>
				<mx:DataGridColumn headerText="Id" dataField="id" />
				<mx:DataGridColumn headerText="Nome" dataField="name" />
			</mx:columns>
		</mx:DataGrid>
		<mx:Button label="List" click="list.send({v:v1.text,v1:v2.text})" />
	</mx:Panel>
</mx:Application>

A interface com o código java neste exemplo é feita via HTTP padrão, utilizando as tags mx:HTTPService, a primeira faz um GET para a URL e por tanto chama a nossa calculadora, a segunda faz um post, buscando o XML do exemplo Java …

Quando o primeiro botão (Calculate) é clicado, o primeiro serviço é chamado [calc.send(parameters)] e o texto na primeira label é atualizado de acordo com o binding configurado.
Quando o segundo botão é clicadl (List) o segundo serviço é chamado [list.send(params)] e os dados da grid serão atualizados automaticamente.

É só isto :D
Dica: se você utilizar list.send sem parâmetros, a tag HTTPservice dispara um GET e não um POST, ou pelo menos foi o que aconteceu com o Flex 2, eu não testei com FLex 3 ainda …

Esta abordagem é bastante simples e não necessita de muito código, mas não parece muito com OOP …


E o Flex tem um suporte excelente a Remoting, então, o que vocês acham de criar um POJO em Java e usa-lo como um objeto remoto no código Flex?
Gostou da idéia?

Então vamos escrever a classe TestService com as duas mesmas operações (calc e list)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.ArrayList;
import java.util.List;
 
public class TestService
{
	public float calc(float v, float v1){
		System.out.format("%f X %f\n", v, v1);
		return v * v1;
	}
 
	public List<TestVo> list(){
		System.out.println("List called");
		ArrayList<TestVo> res = new ArrayList<TestVo>();
		for(int i=0;i<40;i++){
			res.add(new TestVo(i,"Testé  ã " + i));
		}
		return res;
	}
}

E agora não sera necessária toda aquela parafernalha XML (bom, pelo menos a parte de código que criamos antes), podemos utilizar um Value Object simples :D

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class TestVo
{
	private String name;
	private int id;
	public TestVo(int id, String name)
	{
		this.id = id;
		this.name = name;
	}
	public String getName()
	{
		return name;
	}
	public int getId()
	{
		return id;
	}
	public void setName(String name)
	{
		this.name = name;
	}
	public void setId(int id)
	{
		this.id = id;
	}
}

Para usar este serviço a partir do Flash, nos precisamos configurar o OpenAMF usando o arquivo WEB-INF/openamf-config.xml, o nome e local onde este arquivo se encontra foram configurados no web.xml anteriormente.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?xml version="1.0" encoding="UTF-8"?>
<config>
	<amf-serializer>
			<force-lower-case-keys>false</force-lower-case-keys>
	</amf-serializer>
	<invoker>
		<name>Java</name>
		<class>org.openamf.invoker.JavaServiceInvoker</class>
	</invoker>
	<custom-class-mapping>
		<java-class>....TestVo</java-class>
		<custom-class>TestVo</custom-class>
	</custom-class-mapping>
	<service>
		<name>TestService</name>
		<service-location>....TestService</service-location>
		<invoker-ref>Java</invoker-ref>
		<method>
			<name>calc</name>
			<parameter>
				<type>*</type>
			</parameter>
		</method>
		<method>
			<name>list</name>
			<parameter>
				<type>*</type>
			</parameter>
		</method>
	</service>
</config>

Eu limpei quase todo o código do arquivo, removi tudo o que não seria utilizado neste exemplo :D
O OpenAMF tem diversas opções de configuração, é possível chamar diretamente EJBs, WebServices, qualquer classe Java, objetos JMX e Beans do Spring Framework a partir do seu código Flex sem problema algum …

Não é possível utilizar a tag padrão mx:RemoteObject para chamar este serviço, por que o OpenAMF não suporta o AMF3, e eu não consegui encontrar uma forma de configurar a versão do AMF para a tag mx:RemoteObject …

Então, para o primeiro exemplo, vamos utilizar apenas uma instância da classe NetConnection:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
	layout="vertical"
	backgroundColor="#F4F4F4"
	initialize="init()">
	<mx:Script>
		<![CDATA[
            import mx.controls.Alert;
            import mx.rpc.events.ResultEvent;
            import mx.collections.ArrayCollection;   
            [Bindable]
            public var lista:ArrayCollection; 
            public var nc : NetConnection;
 
            function init() : void{
            	nc = new NetConnection();
            	nc.objectEncoding = ObjectEncoding.AMF3;
            	nc.addEventListener(NetStatusEvent.NET_STATUS,netStatus);
            	nc.connect("http://localhost:8080/teste/gateway2");
            }
            function netStatus(event : NetStatusEvent) : void {
            	Alert.show(event.info.code);
            }
            function onRetornaLista(event):void{
            	var e : Array = event;
            	lista = new ArrayCollection(e);
            }       
            function onRetornaCalc(event):void{
            	lbl.text = String(event);
            }
            function calc(v1 : Number, v2 : Number) : void {
            	var r : Responder = new Responder(onRetornaCalc);
            	nc.call("TestService.calc",r,v1,v2);
            }
            function list() : void{
            	var r : Responder = new Responder(onRetornaLista);
            	nc.call("TestService.list",r);
            }
 
        ]]>
	</mx:Script>
	<mx:Panel width="80%" height="531" title="Teste">
		<mx:Label width="100%" color="blue"
			text="Type two numbers and press calculate." />
		<mx:TextInput id="v1" text="3" />
		<mx:TextInput id="v2" text="5" />
		<mx:Label id="lbl"  />
		<mx:Button label="Calculate" click="calc(Number(v1.text),Number(v2.text))" />
		<mx:DataGrid dataProvider="{lista}" width="100%" height="195">
			<mx:columns>
				<mx:DataGridColumn headerText="Id" dataField="id" />
				<mx:DataGridColumn headerText="Nome" dataField="name" />
			</mx:columns>
		</mx:DataGrid>
		<mx:Button label="List" click="list()" />
	</mx:Panel>
</mx:Application>

O método init() apenas criou e configurou a instância NetConnection da aplicação (nc).
Os botões realizam as mesmas operações do exemplo anterior, mas agora é utilizada a sintaxe Classe.metodo, por exemplo TestService.calc para chamar o método de um serviço via NetConnection.

A novidade aqui é que precisamos dos Responders para tratar os retornos de cada método.
Eu acho esta abordagem um pouco melhor, mas ainda tenho esta sensação de estar fazendo alguma coisa errada …
Então, procurando pela internet eu encontrei a biblioteca RemoteObjectAMF0
Esta é uma pequena biblioteca escrita em ActionScript que cria uma tag nova para chamar serviços AMF0 a partir de uma aplicação Flex2.
Para utiliza-la basta fazer estas pequenas alterações na aplicação anterior …

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
	xmlns:renaun="com.renaun.rpc.*" layout="vertical"
	backgroundColor="#F4F4F4">
	<mx:Script>
		<![CDATA[
            import mx.controls.Alert;
            import mx.rpc.events.ResultEvent;
            import mx.collections.ArrayCollection;   
            [Bindable]
            public var lista:ArrayCollection; 
 
            NetConnection.defaultObjectEncoding = ObjectEncoding.AMF0;
 
            function onRetornaLista(event:ResultEvent):void{
            	lista = ArrayCollection(event.result);
            }       
            function onRetornaCalc(event:ResultEvent):void{
            	lbl.text = String(event.result);
            }
        ]]>
	</mx:Script>
	<renaun:RemoteObjectAMF0
		endpoint="http://localhost:8080/teste/gateway2" id="TST"
		source="....TestService" showBusyCursor="true"
		makeObjectsBindable="true"
		fault="Alert.show(String(event.fault)), 'Error'">
		<renaun:methods>
			<renaun:method name="list" result="onRetornaLista(event)"></renaun:method>
			<renaun:method name="calc" result="onRetornaCalc(event)"></renaun:method>
		</renaun:methods>
	</renaun:RemoteObjectAMF0>
	<mx:Panel width="80%" height="531" title="Teste">
		<mx:Label width="100%" color="blue"
			text="Type two numbers and press calculate." />
		<mx:TextInput id="v1" text="3" />
		<mx:TextInput id="v2" text="5" />
		<mx:Label id="lbl"  />
		<mx:Button label="Calculate" click="TST.calc(Number(v1.text),Number(v2.text))" />
		<mx:DataGrid dataProvider="{lista}" width="100%" height="195">
			<mx:columns>
				<mx:DataGridColumn headerText="Id" dataField="id" />
				<mx:DataGridColumn headerText="Nome" dataField="name" />
			</mx:columns>
		</mx:DataGrid>
		<mx:Button label="List" click="TST.list()" />
	</mx:Panel>
</mx:Application>

Ainda não ficou perfeito, esta quase tão bom quanto com as tags de remoting nativas, mas não tão bom quanto, mesmo assim é a melhor possibilidade deste post :D
Eu não gosto da idéia de precisar informar o nome completo da classe Java no flex para chamar um serviço Java, acho que a minha solução final vai ser algo parecido com um proxy usando uma NetConnection como backend, mas esta ja esta suficiente por enquanto :D

Eu espero que este post ajude algum de vocês, eu passei dois dias procurando uma solução aceitável para este tipo de comunicação, ja que infelizmente no meu projeto atual, o Lifecycle Data Services não á uma opção viável …

PS.: para compilar os arquivos .mxml você pode utilizar o comando mxmlc do Flex 2 SDK
PS2.: Você precisara corrigir as URLs para o caminho completo da aplicação
PS3.: os é e ã não são problemas com a codificação da página, eu adicionei eles propositalmente para ver como seria tratado o encoding diferente no flex e no java, e tudo correu muito bem :D

Se você gostou deste post, lembre-se de assinar o RSS feed do blog, para ser notificado de novos posts!

Flex2/ActionScript 3 - Transformando objetos em XML

Trabalhando com Flex com um backend em Java, cheguei a conclusão que a melhor alternativa para o projeto seria simplesmente fazer posts de dados do flex para o java e receber XML de volta (o Flex trabalha muito bem com XML).
Mas tem um problema nisto, passar um ou dois valores desta forma é bastante fácil, mas quando eu precisava passar objetos complexos era muito chato passar milhares de parâmetros para uma URL, então tive a idéia de mandar também XML de volta para o Java …
desta forma, o único problema que continuou foi eu precisar escrever manualmente o XML para cada post, sendo que na maioria das vezes eu ja tinha um objeto Flex …
Solucionei isto com uma pequena função utilitária que transforma qualquer objeto em XML, tentei procurar alguma coisa na API padrão, mas não encontrei nada, então escrevi isto:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package teste
{
	import mx.utils.ObjectUtil;
 
	public class Utils
	{
		public static function objectToXml(obj : Object, name : String) : XML{
			var result : XML;
			var info:Object = ObjectUtil.getClassInfo(obj);
			if(name==null)
				name = info.name;
			result = new XML("<" + name + "></"+ name + ">");
			for each (var qn : QName in info.properties){
				var val : Object = obj[qn.toString()];
				if(ObjectUtil.isSimple(val))
					result[qn.toString()] = val;
				else
					result.appendChild(objectToXml(val,qn.toString()));
			}
			return result;			
		}
	}
}

Para usar também é bem fácil:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" initialize="appInit()">
<mx:Script>
	<![CDATA[
		import teste.Utils;
		import mx.utils.ObjectUtil;
		import mx.controls.Alert;
		public function appInit() : void {
			var obj : Object = {name:'teste',address:{street:'rua',number:20}};
			Alert.show(Utils.objectToXml(obj,'teste').toXMLString());
		}
	]]>
</mx:Script>
 
</mx:Application>

e o XML gerado no exemplo é este:

1
2
3
4
5
6
7
<teste>
  <address>
    <number>20</number>
    <street>rua</street>
  </address>
  <name>teste</name>
</teste>

Bom, não sei se esta é a melhor forma de se trabalhar, mas até que gostei da solução :D

Se você gostou deste post, lembre-se de assinar o RSS feed do blog, para ser notificado de novos posts!