Я лично не связан с Adobe, Google или Sun, но мне нравится кодировать на Java. Я рад видеть, что Adobe заполняет пробел, который Sun оставил много лет назад апплетами. Java является идеальным выбором для использования в Google App Engine. Так может ли Flex быть хорошим выбором для использования с обоими? В последнем эпизоде мы позволили Энакину встретиться с Оби Ван Кеноби, теперь пришло время посмотреть, как они справляются вместе в более сложных миссиях. Давайте погрузимся глубже и посмотрим …
Я предполагаю, что у вас есть базовые знания о GAE и его плагине Eclipse. Если нет, вернитесь к предыдущей статье и сделайте немного кодирования, потому что эта часть будет сложной. Я также предполагаю, что вы знаете, что такое Flex (или, по крайней мере, RIA) и что он предлагает, в противном случае вы можете сделать это руководство с любой другой веб-платформой и все же достичь нашей цели в этой статье.
В прошлой статье нам удалось закодировать очень простой сервлет, который возвращает и получает XML от нашего клиента Flex, который имеет жестко закодированные теги в виде строк. Конечно, в реальном мире вы бы этого не хотели и, вероятно, использовали бы xstream или любой другой парсер для преобразования наших объектов в xml. Но разве не было бы замечательно вызывать бэкэнд Java из нашего клиента Flex с удаленным взаимодействием и использованием объектов без xml-сериализации. В этом эпизоде мы собираемся построить некоторые bean-компоненты Spring на GAE и сделать их доступными для удаленного взаимодействия BlazeDS для нашего клиента Flex.
Опять же, если вы не пробовали предыдущий пост или вам не по душе GAE, попробуйте.
Давайте сделаем вступление в стиле Head First, наш босс и клиенты были довольны проектом телефонной книги, который мы создали в прошлой части, но вскоре они будут требовать более высокой производительности и большей функциональности, поэтому давайте сделаем рефакторинг нашего предыдущего проекта и превратим его в более сложный.
Создайте новый проект GAE, как и предыдущий, на этот раз назовем его SecondProject.
Снова плагин создал пример для нас, давайте забудем о них и начнем с нашей сущности. Создайте класс Java в пакете org.flexjava.server.
package org.flexjava.server;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
@PersistenceCapable
public class Entry {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
Long id;
@Persistent
private String name;
@Persistent
private String phone;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
//getters setters
}
Создайте новый интерфейс FlexService в пакете org.flexjava.server со следующими объявлениями методов.
package org.flexjava.server;
import java.util.List;
public interface FlexService {
public boolean addEntry(Entry entry);
public List <Entry> getEntryList();
}
Этих двух методов будет достаточно для достижения нашей цели. Теперь в том же пакете создайте класс FlexServiceImpl и добавьте методы со знаниями, полученными из нашего предыдущего приложения.
package org.flexjava.server;
import java.util.List;
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
import javax.jdo.annotations.Transactional;
public class FlexServiceImpl implements FlexService {
//persistance manager
private static final PersistenceManagerFactory pmfInstance = JDOHelper
.getPersistenceManagerFactory("transactions-optional");
PersistenceManager persistenceManager;
@Override
@Transactional
public boolean addEntry(Entry entry) {
try {
//check if the id is 0 which is same as null
if (entry.getId().intValue()==0){
entry.setId(null);
}
//get manager
persistenceManager = pmfInstance.getPersistenceManager();
//save our entry
persistenceManager.makePersistent(entry);
return true;
} catch (Exception e) {
return false;
}
}
@Override
@Transactional
@SuppressWarnings("unchecked")
public List<Entry> getEntryList() {
//get persistance manager
persistenceManager = pmfInstance.getPersistenceManager();
//our query which is a select * in this case
String query = "select from " + Entry.class.getName();
//return the result
return (List<Entry>) persistenceManager.newQuery(query).execute();
}
}
Compared to the last tutorial our server side code is much simpler. Before going into dungeons of configuration files lets code the Flex end. Click on the project and add Flex nature. Do not forget to change the output folder to “war” folder of the project. To use remoting we need the ActionScript counterpart of our Entry object. Create a new ActionScript object under org.flexjava.client package.
package org.flexjava.client
{
[Bindable]
[RemoteClass(alias="org.flexjava.server.Entry")]
public class Entry
{
public var id:int;
public var name:String;
public var phone:String;
public function Entry()
{
}
}
}
The remote class alias must be pointing exactly to your Java object. Since we have done this we are ready to finish our Flex client. It will be very similar to the previous phonebook application.
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" initialize="init()">
<mx:TextInput x="114" y="20" id="nameTxt"/>
<mx:TextInput x="114" y="60" id="phoneTxt"/>
<mx:Label x="29" y="22" text="Name"/>
<mx:Label x="29" y="62" text="Phone"/>
<mx:Button x="332" y="60" label="Save" click="saveEntry()"/>
<mx:DataGrid id="grid" x="29" y="138" dataProvider="{phonebook}">
<mx:columns>
<mx:DataGridColumn headerText="Name" dataField="name"/>
<mx:DataGridColumn headerText="Phone" dataField="phone"/>
<mx:DataGridColumn headerText="Record Id" dataField="id"/>
</mx:columns>
</mx:DataGrid>
<mx:RemoteObject id="ro" result="showEntryList(event)" destination="FlexDestination" endpoint="http://localhost:8080/messagebroker/amf"/>
<mx:Script>
<![CDATA[
import mx.binding.utils.BindingUtils;
import org.flexjava.client.Entry;
import mx.collections.ArrayCollection;
import mx.rpc.events.ResultEvent;
import mx.controls.Alert;
[Bindable]
public var phonebook:ArrayCollection;
public function init():void{
ro.getEntryList();
}
public function saveEntry():void{
var entry:Entry=new Entry();
entry.name=nameTxt.text;
entry.phone=phoneTxt.text;
ro.addEntry(entry);
}
public function showEntryList(event:ResultEvent):void{
if (event.result!=null){
if (event.result==false){
Alert.show("record could not be added");
}else if (event.result==true){
Alert.show("added to your phone book");
ro.getEntryList();
}else{
phonebook=event.result as ArrayCollection;
}
}
}
]]>
</mx:Script>
</mx:Application>
This way, the Flex client looks simpler too. We are done with coding and it was still quite easy. Lets do the Spring and Blaze configuration. If you ever googled Flex-Java and Blaze you probably ended up at Christophe Coenraets blog. He was one of first people I heard and learned flex from. He offers great examples and a zipped and configured Tomcat with everything necessary (from Blaze, ActiveMQ, database, Spring etc..). Spring source also offers a great documentation about Spring-Blaze integration. We will definitely need this two to continue.
Locate web.xml under war\WEB-INF folder and add the following inside the web-app tags.
<!-- Spring -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/webAppContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>testdrive</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>testdrive</servlet-name>
<url-pattern>/messagebroker/*</url-pattern>
</servlet-mapping>
Now create a webAppContext.xml next to web.xml.
<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:flex="http://www.springframework.org/schema/flex"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/flex
http://www.springframework.org/schema/flex/spring-flex-1.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<flex:message-broker>
<flex:message-service
default-channels="my-streaming-amf,my-longpolling-amf,my-polling-amf" />
<!-- <flex:secured /> -->
</flex:message-broker>
<bean id="flexBean" class="org.flexjava.server.FlexServiceImpl"/>
<!-- Expose the productDAO bean for BlazeDS remoting -->
<flex:remoting-destination destination-id="FlexDestination" ref="flexBean" />
</beans>
This will make our FlexServiceImpl class available as a Spring bean called flexBean. Next we enable this Spring bean as a Blaze remoting destination with the id of FlexDestination. FlexDestination will be the name exposed to outer world to call our service class with remoting.
Now create a folder named flex under WEB-INF and add the following four flex xml configuration files. These files are quite standard so if you have downloaded Christophe’s example or got them any other way, you may just copy those files instead of creating them. You can also get them under my project export.
Finally the jar list. Actually I just copy the jars Christophe’s server, I know most of the jars we won’t be using (specially database one) but still I took them all to be able to add new functionality to my project.
antlr-3.0.1.jar
aopalliance.jar
appengine-api-1.0-sdk-1.2.0.jar
aspectjrt.jar
aspectjweaver.jar
backport-util-concurrent.jar
cglib-nodep-2.1_3.jar
commons-codec-1.3.jar
commons-httpclient-3.0.1.jar
commons-logging.jar
concurrent.jar
datanucleus-appengine-1.0.0.final.jar
datanucleus-core-1.1.0.jar
datanucleus-jpa-1.1.0.jar
flex-messaging-common.jar
flex-messaging-core.jar
flex-messaging-opt.jar
flex-messaging-proxy.jar
flex-messaging-remoting.jar
geronimo-jpa_3.0_spec-1.1.1.jar
geronimo-jta_1.1_spec-1.1.1.jar
gwt-servlet.jar
jackson-core-asl-0.9.9-6.jar
jdo2-api-2.3-SNAPSHOT.jar
org.springframework.aop-3.0.0.M3.jar
org.springframework.asm-3.0.0.M3.jar
org.springframework.aspects-3.0.0.M3.jar
org.springframework.beans-3.0.0.M3.jar
org.springframework.context-3.0.0.M3.jar
org.springframework.context.support-3.0.0.M3.jar
org.springframework.core-3.0.0.M3.jar
org.springframework.expression-3.0.0.M3.jar
org.springframework.flex-1.0.0.RC1.jar
org.springframework.jdbc-3.0.0.M3.jar
org.springframework.jms-3.0.0.M3.jar
org.springframework.transaction-3.0.0.M3.jar
org.springframework.web-3.0.0.M3.jar
org.springframework.web.servlet-3.0.0.M3.jar
spring-security-acl-2.0.4.jar
spring-security-catalina-2.0.4.jar
spring-security-core-2.0.4.jar
spring-security-core-tiger-2.0.4.jar
spring-security-taglibs-2.0.4.jar
xalan.jar
So far we did everything as we are instructed so we expect everything to run smoothly. Right click and run your project as Web Application.
"Channel.Connect.Failed error NetConnection.Call.Failed:
HTTP: Status 500: url: 'http://localhost:8080/messagebroker/amf'"
Ok, we weren’t expecting this but if we check our server logs we will notice a clue;
WARNING: /messagebroker/amf
java.lang.RuntimeException: Session support is not enabled in appengine-web.xml.To enable sessions, put true in that file. Without it, getSession() is allowed, but manipulation of sessionattributes is not.
at com.google.apphosting.utils.jetty.StubSessionManager$StubSession.throwException(StubSessionManager.java:67)
Great! Google even tells us what to do, this is what I expect from a framework. Open the appengine-web.xml file and add ”true” just before the system-parameters tag. Lets re run the server and our application works! Well not that hard, actually it is time to deploy our code in to the Google Cloud and see it working there.
First go to your SecondProject.mxml file and change the endpoint value with your app url(in mine; endpoint=»http://flexjava2p.appspot.com/messagebroker/amf»). Click the Deploy App Engine project button, and enter your email and password. Of course if you don’t have an GAE account first create one.
The plugin recompiles and deploys our project. Now open a browser and type your url. Ok this was the first real suprise GAE introduced to me. So far GAE offered a reliable and stable development and production environment but now we face an error which we can not reproduce in our development environment “flex.messaging.request.DuplicateSessionDetected “. This not very cool, the development environment differs from actual runtime and we are facing a problem which we did not face in development.
When I search for this error I found another good resource by Martin Zoldano. In this post Martin tells us about the problem and how he solved by editing flex-messaging-core.jar. This incident cools down my excitement a bit about GAE/Flex but still you can see how important it is to work with open source frameworks. I asked Martin to send me his custom jar and (thanks to him) I included the jar in the project export attachment below. Besides the custom jar file, go to services-config.xml and add «<manageable>false</manageable>» just after the system tag.
Feel lost? Do not.. GAE Java is just in the early stage and even getting to this point with even Flex which is totally out of Java environment is still a great success. As long as we have the opened source we will have solutions like Martin Zoldano’s. Even in this early stage we managed to make GAE work with Flex via Blaze.
I remember reading the following on Yakov Fain’s blog;
He was saying; “99% of the people who reject using the software until it gets open sourced, neither plan nor will look at its source code. “
My Comment was;
I agree most people do not look at the source code even if they need but I post a comment saying;
“Well probably same amount of people who buy cars equipped with airbags also don’use the airbags (so far i didn’t) but it can be very helpful if you are in need “.
So when choosing a car don’t forget to get a one with airbags even if you feel you don’t need them. By the way want to see the finished app in the cloud… http://flexjava2p.appspot.com/
Murat Yener