I want to show you how to embed Jetty Server in an Adobe Air application and use BlazeDS for communication. The use case is simple access a MySQL server directly from Adobe AIR but with this option you might experiment and create better use cases for this.
We need to download the following:
- Jetty library (i use jetty-all-7.4.0.RC0.jar)
- Servlet api (i use servlet-api-2.5.jar)
- Mysql Connector
- BlazeDS 4
Let's coding:
First we create a simple table for sample purposes:
Java side:
First we create a simple Java Project and add the 2 jars in the classpath.
Product.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package entities; | |
import java.math.BigDecimal; | |
public class Product { | |
private int productID; | |
private String productName; | |
private BigDecimal productPrice; | |
public int getProductID() { | |
return productID; | |
} | |
public void setProductID(int productID) { | |
this.productID = productID; | |
} | |
public String getProductName() { | |
return productName; | |
} | |
public void setProductName(String productName) { | |
this.productName = productName; | |
} | |
public BigDecimal getProductPrice() { | |
return productPrice; | |
} | |
public void setProductPrice(BigDecimal productPrice) { | |
this.productPrice = productPrice; | |
} | |
} |
Here i use plain JDBC but you can use JPA, Hibernate, etc.
ProductDAO.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package dao; | |
import java.sql.Connection; | |
import java.sql.PreparedStatement; | |
import java.sql.ResultSet; | |
import java.sql.SQLException; | |
import java.util.ArrayList; | |
import java.util.List; | |
import java.util.logging.Level; | |
import java.util.logging.Logger; | |
import entities.Product; | |
public class ProductDAO { | |
private Connection conexion; | |
public ProductDAO(){ | |
conexion = JDBCHelper.getConexion(); | |
} | |
public List<Product> getProducts(){ | |
List<Product> products = new ArrayList<Product>(); | |
try{ | |
PreparedStatement pstmt = conexion.prepareStatement("SELECT * FROM Product"); | |
ResultSet rs = pstmt.executeQuery(); | |
while(rs.next()){ | |
Product product = new Product(); | |
product.setProductID(rs.getInt(1)); | |
product.setProductName(rs.getString(2)); | |
product.setProductPrice(rs.getBigDecimal(3)); | |
products.add(product); | |
} | |
}catch(SQLException ex){ | |
Logger.getLogger(ProductDAO.class.getSimpleName()).log(Level.SEVERE, ex.getMessage()); | |
} | |
return products; | |
} | |
} |
ProductServices.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package services; | |
import java.util.List; | |
import dao.ProductDAO; | |
import entities.Product; | |
public class ProductService { | |
private ProductDAO productDAO = new ProductDAO(); | |
public List<Product> getProducts(){ | |
return productDAO.getProducts(); | |
} | |
} |
This is a very important class, here we are going to create the Server instance and start it.
Note: I assume that you know something about embedding servers, if you want some background here you can find a great resource.
BlazeDSServer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.io.BufferedReader; | |
import java.io.InputStreamReader; | |
import java.net.InetAddress; | |
import java.net.ServerSocket; | |
import java.net.Socket; | |
import java.util.logging.Level; | |
import java.util.logging.Logger; | |
import org.eclipse.jetty.server.Connector; | |
import org.eclipse.jetty.server.Server; | |
import org.eclipse.jetty.server.bio.SocketConnector; | |
import org.eclipse.jetty.webapp.WebAppContext; | |
public class BlazeDSServer { | |
private static Server server = new Server(); | |
public static void main(String[] args) throws Exception { | |
SocketConnector connetor = new SocketConnector(); | |
connetor.setPort(8080); | |
server.setConnectors(new Connector[] {connetor}); | |
WebAppContext context = new WebAppContext(); | |
context.setDescriptor("WEB-INF/web.xml"); | |
context.setResourceBase("webapp"); | |
context.setContextPath("/demoapp"); | |
context.setParentLoaderPriority(true); | |
server.setHandler(context); | |
server.setStopAtShutdown(true); | |
MonitorThread monitor = new MonitorThread(); | |
monitor.start(); | |
server.start(); | |
server.join(); | |
} | |
private static class MonitorThread extends Thread { | |
private ServerSocket socket; | |
public MonitorThread(){ | |
setDaemon(true); | |
setName("StopMonitor"); | |
try{ | |
socket = new ServerSocket(8079, 1, InetAddress.getLocalHost()); | |
Logger.getLogger(BlazeDSServer.class.getSimpleName()).log(Level.INFO, "Close Thread Started"); | |
}catch(Exception ex){ | |
throw new RuntimeException(); | |
} | |
} | |
@Override | |
public void run() { | |
super.run(); | |
Socket accept; | |
try{ | |
accept = socket.accept(); | |
BufferedReader reader = new BufferedReader(new InputStreamReader(accept.getInputStream())); | |
reader.readLine(); | |
server.stop(); | |
accept.close(); | |
socket.close(); | |
Logger.getLogger(BlazeDSServer.class.getSimpleName()).log(Level.INFO, "Server Stopped"); | |
}catch(Exception ex){ | |
throw new RuntimeException(ex); | |
} | |
} | |
} | |
} |
Now we need to extract the BlazeDS.war content. create a folder called webapp inside your src folder with this content, your project structure should look like this.
The last thing you need to do is declare a remote destination on your services-config.xml
The last thing you need to do is declare a remote destination on your services-config.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<service id="remoting-service" | |
class="flex.messaging.services.RemotingService"> | |
<adapters> | |
<adapter-definition id="java-object" class="flex.messaging.services.remoting.adapters.JavaAdapter" default="true"/> | |
</adapters> | |
<default-channels> | |
<channel ref="my-amf"/> | |
</default-channels> | |
<destination id="productService"> | |
<properties> | |
<source>services.ProductService</source> | |
</properties> | |
</destination> | |
</service> |
Now we code the Air side:
Fist create an asset folder inside your air src folder and copy all the bin folder from your java project
As you can see i made the same copy into the bin-debug folder, i don't know why it doesn't export all the files to the bin-debug.
Now let's code the application, first in the descriptor file enable the extended desktop profile so you can use Native Process
Now the code:
EmbedJetty.mxml
As you can see i made the same copy into the bin-debug folder, i don't know why it doesn't export all the files to the bin-debug.
Now let's code the application, first in the descriptor file enable the extended desktop profile so you can use Native Process
Now the code:
EmbedJetty.mxml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" | |
xmlns:s="library://ns.adobe.com/flex/spark" | |
xmlns:mx="library://ns.adobe.com/flex/mx" | |
width="500" height="500" | |
creationComplete="creationCompleteHandler(event)"> | |
<fx:Script> | |
<![CDATA[ | |
import mx.collections.ArrayCollection; | |
import mx.controls.Alert; | |
import mx.events.FlexEvent; | |
import mx.rpc.events.FaultEvent; | |
import mx.rpc.events.ResultEvent; | |
[Bindable] | |
private var products:ArrayCollection; | |
//The native process | |
private var nativeProcess:NativeProcess; | |
private var nativeProcessStartupInfo:NativeProcessStartupInfo; | |
private var executable:File = new File("C:/Program Files/Java/jdk1.7.0/bin/java.exe"); | |
//The socket | |
private var socket:Socket; | |
protected function btnStartServer_clickHandler(event:MouseEvent):void | |
{ | |
if(nativeProcess == null){ | |
nativeProcess = new NativeProcess(); | |
nativeProcessStartupInfo = new NativeProcessStartupInfo(); | |
} | |
nativeProcessStartupInfo.executable = executable; | |
Alert.show(File.applicationDirectory.nativePath); | |
nativeProcessStartupInfo.workingDirectory = new File(File.applicationDirectory.nativePath +"/assets/"); | |
var args:Vector.<String> = new Vector.<String>(); | |
args.push("-classpath"); | |
args.push(".;lib/jetty-all-7.4.0.RC0.jar;lib/servlet-api-2.5.jar;lib/mysql-connector.jar"); | |
args.push("BlazeDSServer"); | |
nativeProcessStartupInfo.arguments = args; | |
nativeProcess.start(nativeProcessStartupInfo); | |
nativeProcess.addEventListener(ProgressEvent.STANDARD_ERROR_DATA, nativeProcess_errorHandler); | |
} | |
protected function creationCompleteHandler(event:FlexEvent):void | |
{ | |
if(!NativeProcess.isSupported){ | |
Alert.show("Native Process not supported"); | |
} | |
if(!executable.exists){ | |
Alert.show("Java is not found on your machine"); | |
} | |
} | |
protected function btnStopServer_clickHandler(event:MouseEvent):void | |
{ | |
socket = new Socket(); | |
socket.addEventListener(Event.CONNECT, socket_connectHandler); | |
socket.addEventListener(IOErrorEvent.IO_ERROR, socket_onError); | |
socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, socket_onSecError); | |
try{ | |
socket.connect("192.168.1.34", 8079); | |
}catch(e:Error){ | |
Alert.show(e.message); | |
} | |
} | |
protected function btnGetData_clickHandler(event:MouseEvent):void | |
{ | |
getProductsCR.token = productRO.getProducts(); | |
} | |
protected function nativeProcess_errorHandler(event:ProgressEvent):void | |
{ | |
var temp:NativeProcess = event.target as NativeProcess; | |
var salida:String = temp.standardError.readUTFBytes(temp.standardError.bytesAvailable); | |
txtLog.text += salida + File.lineEnding; | |
} | |
protected function socket_connectHandler(event:Event):void | |
{ | |
socket.writeUTFBytes("\n"); | |
socket.flush(); | |
} | |
protected function socket_onError(event:IOErrorEvent):void | |
{ | |
Alert.show(event.text); | |
} | |
protected function socket_onSecError(event:SecurityErrorEvent):void | |
{ | |
Alert.show(event.text); | |
} | |
protected function productRO_faultHandler(event:FaultEvent):void | |
{ | |
Alert.show(event.fault.message, "Error"); | |
} | |
protected function getProductsCR_resultHandler(event:ResultEvent):void | |
{ | |
products = event.result as ArrayCollection; | |
} | |
]]> | |
</fx:Script> | |
<fx:Declarations> | |
<s:RemoteObject id="productRO" destination="productService" endpoint="http://192.168.1.34:8080/demoapp/messagebroker/amf" | |
fault="productRO_faultHandler(event)"/> | |
<s:CallResponder id="getProductsCR" result="getProductsCR_resultHandler(event)"/> | |
</fx:Declarations> | |
<s:layout> | |
<s:VerticalLayout paddingBottom="10" paddingLeft="10" paddingRight="10" paddingTop="10"/> | |
</s:layout> | |
<s:HGroup width="100%" minHeight="100" height="50" > | |
<s:Button id="btnStartServer" label="Start Server" click="btnStartServer_clickHandler(event)"/> | |
<s:Button id="btnStopServer" label="Stop Server" click="btnStopServer_clickHandler(event)"/> | |
</s:HGroup> | |
<s:HGroup width="100%"> | |
<s:Button id="btnGetData" label="Get Data" click="btnGetData_clickHandler(event)"/> | |
</s:HGroup> | |
<s:HGroup width="100%" height="100%"> | |
<s:DataGrid id="dgData" width="100%" height="100%" dataProvider="{products}"> | |
<s:columns> | |
<s:ArrayCollection> | |
<s:GridColumn headerText="Product ID" dataField="productID"/> | |
<s:GridColumn headerText="Product Name" dataField="productName"/> | |
<s:GridColumn headerText="Product Price" dataField="productPrice"/> | |
</s:ArrayCollection> | |
</s:columns> | |
</s:DataGrid> | |
</s:HGroup> | |
<s:Group width="100%"> | |
<s:TextArea id="txtLog" color="red" width="100%" height="100%"/> | |
</s:Group> | |
</s:WindowedApplication> |
Now let's take a more closer view to the code:
- First the executable file will be the Java path so it depends on your machine.
- I use NativeProcess to start the embed Jetty instance and a socket to shutdown the embed jetty instance.
- The arguments for the NativeProcessStartupInfo are the necessary classes to make the Java desktop app run. In the application:
args.push("-classpath");
args.push(".;lib/jetty-all-7.4.0.RC0.jar;lib/servlet-api-2.5.jar;lib/mysql-connector.jar");
args.push("BlazeDSServer");
- Once it connects you can use RemoteObjects to make rpc call to your Java code.
- You can use messaging too.
When you click the start server button, in the text area you see the log comming from the server.
Stop the server by clicking the Stop Server button.
And that's all, hope this will help you, any question or bug please let me know.
Here is the source code for the Java Project
Here is the Source code for the Flex Project
What if you another executable jar running previously? Air won't be able to run "java" again. At least that's a problem I'm facing currently...
ResponderEliminar