Wednesday, November 19, 2008

Flex z-index and Liferay Portal

We recently encountered an issue when embedding Flex/Flash applications in Liferay Portlets.

The shockwave (.swf) file was showing on top of the liferay navigation menu:













The usual solution is to specify the z-index for the div layers.
There are even some very cool things you can do with Flex overlay.

Liferay however is a third party portal platform and if you don't want to have to change the source code or extend Liferay, a quick fix is to use the wmode argument for the embedded flex application:

<div>
   <embed wmode="transparent" src="<%= request.getContextPath() %>/flex/clinical_data.swf" height=250 width=500>
</div>

You just have to redeploy your portlet and now the menu appears on top of your portlet:

Thursday, November 13, 2008

Liferay Portlet using BlazeDS

In my previous post, I explained why and how I used BlazeDS, a server-based Java remoting and web messaging technology, to call remote Web Services easily from Adobe Flex applications.

In this post I explain how to create a Liferay portlet that uses Flex and BlazeDS.

Creating the Liferay Portlet

Creating a new portlet is straightforward. Under ./liferay-plugins-sdk-5.1.1/portlets at the prompt I just typed the command: create messier_object "Messier Object WS". As a result, a messier_object-portlet folder is created with the skeleton of a working portlet (the first argument of create is the name of the portlet (always postfixed with '-portlet'), the second argument is the title of the portlet).

I then modified the category name of messier_object-portlet/docroot/WEB-INF/liferay-display.xml to ICW.Test.

<display>
   <category name="ICW.Test">
      <portlet id="messier_object">
   </portlet>
</category>

I then added the Flex code by copying my testing Flex Application Test_Flex_WS_messier.mxml under the docroot folder.

Integrating the target shockwave file in the view.jsp file is also straightforward:
<div>
   <embed src="<%= request.getContextPath() %>/Test_Flex_WS_messier.swf" heigth=350 width=350>
</div>
Adding BlazeDS Libraries and Configuration Files

I then added a set of 10 jar files (backport-util-concurrent.jar to flex-messaging-remoting.jar) that came with the turnkey BlazeDS installation sample code inside the WEB-INF/lib folder.

In addition to this, I had to copy the four BlazeDS configuration files:
messaging-config.xml, proxy-config.xml, remoting-config.xml, services-config.xml.

The services defined in these files are generic and can be reuse if you want to access remote data either through Messaging, HTTService, WebService or RPC/AMF remote procedure calls.

Only proxy-config.xml contains a specific web service destination (ws-sesame) to access astronomical data for the application.









Content of messaging-config.xml:

<?xml version="1.0" encoding="UTF-8"?>
<service id="message-service" class="flex.messaging.services.MessageService">
<adapters>
<adapter-definition id="actionscript" class="flex.messaging.services.messaging.adapters.ActionScriptAdapter" default="true" />
<adapter-definition id="jms" class="flex.messaging.services.messaging.adapters.JMSAdapter"/>
</adapters>
<default-channels>
  <channel ref="my-streaming-amf"/>
  <channel ref="my-polling-amf"/>
</default-channels>
</service>

Content of proxy-config.xml:

<?xml version="1.0" encoding="UTF-8"?>
<service id="proxy-service" class="flex.messaging.services.HTTPProxyService">

<properties>
<connection-manager>
<max-total-connections>100</max-total-connections>
<default-max-connections-per-host>2</default-max-connections-per-host>
</connection-manager>
<allow-lax-ssl>true</allow-lax-ssl>
</properties>

<default-channels>
<channel ref="my-http"/>
<channel ref="my-amf"/>
</default-channels>

<adapters>
<adapter-definition id="http-proxy" class="flex.messaging.services.http.HTTPProxyAdapter" default="true"/>
<adapter-definition id="soap-proxy" class="flex.messaging.services.http.SOAPProxyAdapter"/>
</adapters>

<destination id="DefaultHTTP">
<properties>
</properties>
</destination>

<destination id="ws-sesame">
<properties>
<wsdl>http://cdsws.u-strasbg.fr/axis/services/Sesame?wsdl</wsdl>
<soap>*</soap>
</properties>
<adapter ref="soap-proxy"/>
</destination>

</service>


Content of remoting-config.xml:


<?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>
</service>

Content of services-config.xml:


<?xml version="1.0" encoding="UTF-8"?>
<services-config>

<services>
  <service-include file-path="remoting-config.xml" />
  <service-include file-path="proxy-config.xml" />
  <service-include file-path="messaging-config.xml" />
  <default-channels>
     <channel ref="my-amf"/>
  </default-channels>
 </services>

<channels>

  <channel-definition id="my-streaming-amf" class="mx.messaging.channels.StreamingAMFChannel">
      <endpoint url="http://{server.name}:{server.port}/{context.root}/messagebroker/streamingamf" class="flex.messaging.endpoints.StreamingAMFEndpoint"/>
  </channel-definition>

  <channel-definition id="my-amf" class="mx.messaging.channels.AMFChannel">
      <endpoint url="http://{server.name}:{server.port}/{context.root}/messagebroker/amf" class="flex.messaging.endpoints.AMFEndpoint"/>
      <properties>
          <polling-enabled>false</polling-enabled>
      </properties>
  </channel-definition>

  <channel-definition id="my-secure-amf" class="mx.messaging.channels.SecureAMFChannel">
      <endpoint url="https://{server.name}:{server.port}/{context.root}/messagebroker/amfsecure" class="flex.messaging.endpoints.SecureAMFEndpoint"/>
  </channel-definition>

  <channel-definition id="my-polling-amf" class="mx.messaging.channels.AMFChannel">
      <endpoint url="http://{server.name}:{server.port}/{context.root}/messagebroker/amfpolling" class="flex.messaging.endpoints.AMFEndpoint"/>
      <properties>
          <polling-enabled>true</polling-enabled>
          <polling-interval-seconds>4</polling-interval-seconds>
      </properties>
  </channel-definition>

  <channel-definition id="my-http" class="mx.messaging.channels.HTTPChannel">
      <endpoint url="http://{server.name}:{server.port}/{context.root}/messagebroker/http" class="flex.messaging.endpoints.HTTPEndpoint"/>
  </channel-definition>

  <channel-definition id="my-secure-http" class="mx.messaging.channels.SecureHTTPChannel">
      <endpoint url="https://{server.name}:{server.port}/{context.root}/messagebroker/httpsecure" class="flex.messaging.endpoints.SecureHTTPEndpoint"/>
  </channel-definition>

</channels>

<logging>
  <!-- You may also use flex.messaging.log.ServletLogTarget -->
  <target class="flex.messaging.log.ConsoleTarget" level="Error">
      <properties>
          <prefix>[BlazeDS] </prefix>
          <includeDate>false</includeDate>
          <includeTime>false</includeTime>
          <includeLevel>true</includeLevel>
          <includeCategory>false</includeCategory>
      </properties>
      <filters>
          <pattern>Endpoint.*</pattern>
          <pattern>Service.*</pattern>
          <pattern>Configuration</pattern>
      </filters>
  </target>
</logging>

<system>
  <redeploy>
      <enabled>true</enabled>
      <watch-interval>20</watch-interval>
      <watch-file>{context.root}/WEB-INF/flex/services-config.xml</watch-file>
      <watch-file>{context.root}/WEB-INF/flex/proxy-config.xml</watch-file>
      <watch-file>{context.root}/WEB-INF/flex/remoting-config.xml</watch-file>
      <watch-file>{context.root}/WEB-INF/flex/messaging-config.xml</watch-file>      
      <touch-file>{context.root}/WEB-INF/web.xml</touch-file>
  </redeploy>
</system>

</services-config>
Do not forget to copy the definitions of the listener and the MessageBroker Servlet in your web.xml file.

Compiling the Flex Application


To compile the Flex application that will be encapsulated into the portlet, you will need to specify the portlet name as context root:
mxmlc -strict=true -show-actionscript-warnings=true -use-network=true \
-services=WEB-INF/flex/services-config.xml -context-root=messier_object-portlet \
-output=./Test_Flex_WS_messier.swf ./Test_Flex_WS_messier.mxml

Deploying the Portlet


To be able to access BlazeDS from the portlet, you will also need to copy the BlazeDS war file in tomcat under .\bundles\tomcat-6.0.16\webapps.

To compile and deploy the whole portlet just type the command 'ant deploy' at the prompt in the messier_object-portlet folder.

You can then add the portlet and test it:




















Friday, November 7, 2008

How to use BlazeDS for Web Service access

I was recently asked to investigate how quickly aggregate medical content from various sources in a portal environment. One path I explored was to use Adobe Flex as front-end technology and access remote web services such as the ones offered by ICW LifeSensor.

With Flex 3.0 you can easily use a Web Service directly from your MXML or ActionScript code, including .NET based web services.

However there are some restrictions. For security reasons, applications running in Flash Player on client computers can only access remote data sources if one of the following conditions is met:
  • Your SWF file is in the same domain as the remote data source.
  • A cross-domain policy file is installed on the web server hosting the data source.
  • You use a proxy and your SWF file is on the same server as the proxy.
Since the Web services I want to use are not under my control and I know they do not have a cross-domain policy files installed, the only solution left is for me to use a proxy.

The good news is that BlazeDS, in addition to add RPC capabilities to Flex, acts as a Proxy, so won't have to write my own!


Installing and trying BlazeDS
BlazeDS is really easy to install. I choose to download the turnkey version to start because it includes a runtime environment (Apache Tomcat) and lot of samples.


After download and unzip, the only things I had to do was to start the database for the samples (Hypersonic/HSQLDB) and start tomcat and point to http://localhost:8400/samples/. The "Take the test drive" of the tutorial contains a section (sample 2) using web services. The sample code is accessible in .\blazeds_turnkey_3-0-0-544\tomcat\webapps\samples\testdrive-webservice\src\main.mxml:

     <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"   backgroundColor="#FFFFFF">
 
       <mx:WebService id="srv" destination="ws-catalog" useProxy="true" showBusyCursor="true"/>
 
        <mx:DataGrid dataProvider="{srv.getProducts.lastResult}" width="100%" height="100%">
          <mx:columns>
                  <mx:DataGridColumn dataField="productId" headerText="Product Id"/>
                  <mx:DataGridColumn dataField="name" headerText="Name"/>
                  <mx:DataGridColumn dataField="price" headerText="Price"/>
           </mx:columns>
       </mx:DataGrid>
 
       <mx:Button label="Get Data" click="srv.getProducts()"/>
 
   </mx:Application>
The destination of the web service is defined in .\samples\WEB-INF\flex\proxy-config.xml:

   <destination id="ws-catalog">
       <properties>
           <wsdl>http://livecycledata.org/services/ProductWS?wsdl</wsdl>
           <soap>*</soap>
       </properties>
       <adapter ref="soap-proxy"/>
   </destination>

Installing and trying BlazeDS

The next step for me was to try to build a new Flex application from scratch
that uses a web service from an outside domain.
I decided to use one of my favorite free testing Web service,
the "CDS - Centre de Données astronomiques de Strasbourg"
located in Alsace, France which provides access to Astronomical data,
including Messier Objects.

Sesame is one of the apache axis based services hosted by CDS.
In the same way, this new service is declared in the proxy-config.xml file:

      <destination id="ws-sesame">
        <properties>
            <wsdl>http://cdsws.u-strasbg.fr/axis/services/Sesame?wsdl</wsdl>
            <soap>*</soap>
        </properties>
        <adapter ref="soap-proxy"/>
    </destination>

The Flex program is very easy to construct.
I have a list of Messier Objects in a combo box with their IDs (Mxxxx)
that are passed as argument for the SesameXML web service operation (see WDSL file).
The web service call indicated that BlazeDS is used as as proxy (useProxy="true")
and define two ActionScripts methods to handle the result coming back
from the remote service and to handle any error (respectively  getData_result and getData_fault).

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
    <mx:Script>
        <![CDATA[
            import mx.controls.Alert;
            import mx.rpc.events.ResultEvent;
            import mx.rpc.events.FaultEvent;
   import mx.utils.ObjectUtil;            
            // This software uses source code created at the Centre de Données astronomiques de Strasbourg, France.
            private function getData():void { webService.SesameXML.send();}
            private function getData_result(evt:ResultEvent):void {textArea.text = ObjectUtil.toString(evt.result);}
            private function getData_fault(evt:FaultEvent):void {Alert.show(evt.type);}
        ]]>
    </mx:Script>
    
    <mx:WebService id="webService" destination="ws-sesame" useProxy="true">
        <mx:operation name="SesameXML"
                resultFormat="object"
                result="getData_result(event);"
                fault="getData_fault(event);"> 
                <mx:request>
    <name>{messier_object.selectedItem.data}</name>
  </mx:request>
        </mx:operation>
    </mx:WebService>
 <mx:ApplicationControlBar dock="true">
        <mx:Button id="button" label="get Messier Data" click="getData();" />
        <mx:Spacer width="10%"/>
        <mx:ComboBox id="messier_object" width="200">
            <mx:dataProvider>
               <mx:ArrayCollection>
                  <mx:source>
                    <mx:Object label="Crab Nebula" data="M1"/>
                    <mx:Object label="Butterfly Cluster" data="M6"/>
                    <mx:Object label="Butterfly Cluster" data="M6"/>
                    <mx:Object label="Ptolemy Cluster" data="M7"/>
                    <mx:Object label="Lagoon Nebula" data="M8"/>
                    <mx:Object label="Wild Duck Cluster" data="M11"/>
                    <mx:Object label="Great Globular Cluster in Hercules" data="M13"/>
                    <mx:Object label="Eagle Nebula" data="M16"/> 
                    <mx:Object label="Omega Nebula" data="M17"/> 
                    <mx:Object label="Trifid Nebula" data="M20"/> 
                    <mx:Object label="Sagittarius Cluster" data="M22"/> 
                    <mx:Object label="Sagittarius Star Cloud" data="M24"/> 
                  </mx:source>
                </mx:ArrayCollection>
            </mx:dataProvider>
           </mx:ComboBox>
    </mx:ApplicationControlBar>
    <mx:TextArea id="textArea" editable="false" width="100%" height="100%" />
 
</mx:Application>
Building the SWF file

To build your shockwave executable file, it is important to indicate where the services
configuration file is located, so the BlazeDS stub is added to the *.SWF file
running in the browser inside the Flash Player and will make the connection
of the BlazeDS proxy.

   mxmlc -strict=true \
-show-actionscript-warnings=true \
-use-network=true \
-services=WEB-INF/flex/services-config.xml \
-context-root=samples \
-output=testdrive-webservice/main.swf testdrive-webservice/src/main.mxm
In fact, services-config.xml describes the different services that the web application is using:
 <services-config>
    <services>
        <service-include file-path="proxy-config.xml" />
        ...
   </services>
   ....
 </services-config>
You will also need to modify your ./docroot/WEB-INF/web.xml file by adding the definitions of the listener and the MessageBroker Servlet definition.
<web-app>
    <display-name>WebTest</display-name>
    <description>Application with Samples</description>

    <context-param>
        <param-name>flex.class.path</param-name>
        <param-value>/WEB-INF/flex/hotfixes</param-value>
    </context-param>

    <!-- Http Flex Session attribute and binding listener support -->
    <listener>
        <listener-class>flex.messaging.HttpFlexSession</listener-class>
    </listener>

    <!-- MessageBroker Servlet -->
    <servlet>
        <servlet-name>MessageBrokerServlet</servlet-name>
        <display-name>MessageBrokerServlet</display-name>
        <servlet-class>flex.messaging.MessageBrokerServlet</servlet-class>
        <init-param>
            <param-name>services.configuration.file</param-name>
            <param-value>/WEB-INF/flex/services-config.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>MessageBrokerServlet</servlet-name>
        <url-pattern>/messagebroker/*</url-pattern>
    </servlet-mapping>
 
</web-app>
Here is the result after querying information about Messier object (M1 - Crab Nebula):
The next task for me to see how to integrate BlazeDS in a portal environment
such as Liferay and explore authentication to Web Services and security features associated
to the use of BlazeDS.