« Torna a Development

Flex Application Portlets

The Relationship of Rich Internet Applications and JSR-1/268 Portals #

Rich internet applications, or RIAs for short, are self-contained applications. In contrast, the "traditional" web applications are HTML-page-based UI. The JSR-168 portal specification marks one of the highest points of this page-based UI, allowing disparate logical pieces to be displayed on a single page in customizable ways. On the flip side, JSR-168 portal technology is also the hardest web technology to evolve into using RIAs. Flex, Flash, rich AJAX applications and Java applets are all self-contained programs that maintain their own states. In a browser, they are susceptible to page navigation and refreshes. Unfortunately, this is how JSR-168 portals work. A portal page is comprised of portlets, each occupying a section of the screen. When the user clicks on something in a portlet, most likely the whole page is refreshed with the new states of each portlets; this causes every Flash movie on the page to restart. Theoretically, it is possible for a Flex program to save all its states and recover them during each restart; but for rich UI programs, this is never practical.

Why, then, are we even concerned about embedding Flex, Flash, rich AJAX or Java applets in Liferay, if the JSR-168 portal idea is indeed that insane? Why can't we build a Flex portal container, and implement Flex portlets using dynamic modules? Answer: a Flex portal is certainly possible, and even technically desirable; but the reality tells otherwise, at least for a while. Internet will remain the way it has been; JSR-168, being a HTML-page-based solution, works with existing paradigms well. Portal products like Liferay have packed more than enough useful features; this is where their values lie. At the same time, there will be situations where Flex and the like are desirable. For instance, your portal site may host Flash games; more and more reporting tools are using Flex charting and grids; you may have sophisticated operations implemented by Flex.

Bottom line: to use Flex with Liferay, compromises have to be made. You can not expect your Flex applications work seamlessly like normal JSR-168 portlets, and your design has to cope with this natural discrepancy between JSR-168 portals and RIAs.

Note:

The above statement is both true and untrue. If you limit your view to 
JSR-168, then it is true. If you expand your view to include the updated and fully implemented JSR-286 spec upon which the Liferay portal is now based, integration with RIA applications is a matter of course. The limitation is now on whether the RIA framework used supports non-page refreshing actions to be implemented as portlet resource calls. Of course these might be coded by hand if desired. Page refreshs are now, in reality, a function of the portlets on a given page. As such, if when designing a given page only portlets refreshed using ajax via "serveResource" are used, then the argument that there is a discrepency between RIA applications and Portals due to the 'page refresh' argument is moot. RIA applications should integrate smoothly and completly within JSR-286 portals, provided the appropriate considerations are made.}}}

Flex Applications In Liferay Portals #

Housing Flex and Flash Applications in Portlets and Pages #

Flex internet applications are Flash movies. While Flex and Flash applications are developed in very different ways, their deployment and runtime are the same. Flex internet applications are usually used in wrapper HTML files. It is possible to turn the main wrapper HTML file into the source of a portlet, but resizing is a major issue. The normal Flex wrapper can specify relative width and height of the embedded Flash player, but they are relative to the browser window. A maximized portlet most likely just occupies a part of the window.

The best option is probably to use the Liferay embedded page or the IFrame portlet to house Flex, Flash, rich AJAX UI, or Java applets. Within such a page or portlet, the content is displayed in an IFrame control. The Flash player can be sized to 100% of the wrapper HTML within the IFrame control, which can be arbitrarily positioned and sized by the portal. Flex wrappers are not limited to static HTML pages; they can be dynamic JSPs as well.

Developing a Sample Flex Applications to Run On Liferay #

Now that we understand the options of embedding Flex applications in portal pages, it is time to develop a demo. We will develop a simple Flex application that uses BlazeDS to call server-side Java, to list user information with the Liferay API. It will use the current user's authentication for security checks and permission enforcement on the server side. The Flex application will be embedded in its own portal page.

We will develop our Flex-Liferay demo with the Liferay Extension framework. We will use Eclipse to debug our Java code in Liferay, and use Flex Builder to debug our Flex application on Liferay.

Setting Up the Development Environment for Liferay, BlazeDS, and Flex #

Let's use Liferay 5.2.2 and Tomcat 5.5.27 for our Flex-Liferay development experience. We will not detail on each step. Windows convention is used in this article; please adapt for your OS for the path names. The final directory structure for our project will look like this:

C:\MyPortalDev\ext\
C:\MyPortalDev\flex_projects \ C:\MyPortalDev\portal\}}}

If you have your Ext development environment set up already, just do the last step of adding BlazeDS library files. Otherwise, follow these steps:

  1. Have JDK (1.5 or 1.6) and Ant (1.7 or higher) installed, with JAVA_HOME and ANT_HOME set appropriately.
  2. Install the Liferay with Tomcat.
    1. Create directory: C:\MyPortalDev\.
    2. Unzip liferay-portal-tomcat-5.5-5.2.2.zip into C:\MyPortalDev\, and rename lifera-portal-5.2.2\ to portal\. In that directory, you will find a tomcat-5.5.27\ directory and some others. More directories may be created there by the Liferay portal at runtime.
    3. Remove the sample Liferay webapp, 7-Cogs. To do that, go to C:\MyPortalDev\portal\tomcat-5.5.27\webapps\, and delete these directories: sevencogs-hook\, sevencogs-theme\, and wol-portlet\.
  3. Install the Liferay Ext environment.
    1. Unzip liferay-portal-ext-5.2.2.zip into C:\MyPortalDev\, and rename liferay-portal-ext-5.2.2\ to ext\.
    2. Check everything inside ext\ into the source control system such as a Subversion or Perforce repository, with a note similar to "Initial Liferay Ext 5.2.2 install". This is very important to maintain visibility of your own files and changes, given that there are so many files involved. You may choose to clean up some unwanted pieces, such as never-used SQL scripts and data.
    3. Configure the Ext environment in preparation of building.
      1. Copy from build.properties to build.${username}.properties, clean up unwanted parts and set up your preferences.
      2. Copy from app.server.properties to app.server.${username}.properties. Make sure app.server.type is set to tomcat and app.server.tomcat.dir to ${project.dir}/../portal/tomcat-5.5.27.
  4. Add all the BlazeDS jar files (flex-messaging-.jar) into the Ext environment under ext\ext-lib\portal\.
  5. Use MySQL as database.
    1. Make sure MySQL is running and has a database called lportal, accessible by user liferay with password liferay. There is no need to create schema yourself.
    2. Set the JDBC options in ext\ext-impl\src\portal-ext.properties with lines like these:

#

  1. MySQL

jdbc.default.driverClassName=com.mysql.jdbc.Driver jdbc.default.url=jdbc:mysql://localhost/lportal?useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false jdbc.default.username=liferay jdbc.default.password=liferay}}}

Now, go to C:\MyPortalDev\ext\, and run ant deploy. It will build and deploy the Liferay webapp to C:\MyPortalDev\portal\tomcat-5.5.27\webapps\ROOT\. When this is done, go to tomcat-5.5.27\bin\, and run startup.bat. Liferay server will start, and a browser window will be launched. Login as test@liferay.com with password test. If you can go this far, you have successfully set up your development environment, and ready for Flex-Liferay development.

Developing Flex Application on Liferay #

"Hello, Flex Life!" #

Let's create our first Flex application that runs on Liferay. In your Flex Builder, create a Flex project called flexlife in C:\MyPortalDev\flex_project\, and set its output directory to C:\MyPortalDev\porta\tomcat-5.5.27\webapps\ROOT\flexlife\. Write a simple Flex screen, like:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"> <mx:Label text="Hello, Flex Life!" fontSize="28" /> </mx:Application>}}}

This will build into a Flex application on the Tomcat server; the wrapper HTML file is webapps\flexlife\flexlife.html.

Log on to Liferay, add a page named Flex Life. Select Manage Pages, and set its type to be Embedded and URL to be /flexlife/flexlife.html. Go back to portal mode, and you will see this:

Flex Call Server-Side Java via BlazeDS #

In ext\ext-impl\src\, create a Java file as test\flexlife\FlexLifeFacade.java like so:

package test.flexlife;

public class FlexLifeFacade { public String echo(String msg) { return "Message received at " + new java.util.Date() + ": " + msg; } } }}}

Create a BlazeDS configuration file, services-config.xml, in C:\MyPortalDev\ext\ext-web\docroot\WEB-INF\flex\:

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

<services> <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="FlexLifeServices"> <properties> <source>test.flexlife.FlexLifeFacade</source> <scope>application</scope> </properties> </destination> </service> </services>

<channels> <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> <add-no-cache-headers>false</add-no-cache-headers> <polling-enabled>false</polling-enabled> </properties> </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>

</services-config>}}}

In C:\MyPortalDev\ext\ext-web\docroot\WEB-INF\web.xml, add a servlet and its mapping:

<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>}}}

We have just coded and configured a server-side Java class callable by remote clients via BlazeDS. Shut down Liferay server if it is running, go to C:\MyPortalDev\ext\ and run ant deploy.

Now it is time to extend our Flex application to call the server-side Java. The application will take user’s input, call the server and append the result in a TextArea.

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">

<mx:Script><![CDATA[ import mx.rpc.events.ResultEvent; private function result(e:ResultEvent):void{ console.text = console.text + '\n' + e.result; } ]]></mx:Script>

<mx:RemoteObject id="ro" result="result(event)" endpoint="/messagebroker/amf" destination="FlexLifeServices"/>

<mx:VBox width="100%"> <mx:Label text="Input:" /> <mx:TextInput id="input" enter="ro.echo(input.text)" width="500" /> <mx:Label text="Result:" /> <mx:TextArea id="console" width="500" height="200" editable="false" /> </mx:VBox>

</mx:Application>}}}

After building it in Flex Builder, go to the Liferay page and refresh to see the new Flex application. (You may need to clear browser cache first to see the new Flex application, since many browsers cache Flash movies aggressively.) Type in something in the text input box and hit enter. If you see an echoed message, that is return from server-side!

If you are familiar with Flex and BlazeDS, you will quickly realize this is just the normal way of using Flex and BlazeDS, except that the Flex application is displayed in a portal page.

We have learned the basics of Flex-Liferay programming. In the rest of this article, we will discuss Liferay-specific Flex development topics.

Calling Liferay APIs for the Flex Client #

Liferay API exposes all its internals through Java. You can programmatically do anything that the portal UI does. We will create a Flex program to list all the Liferay users. We will write a Java method (in FlexLifeFacade.java) to get all the Liferay users, pack the information in an array of UserVO objects. On the Flex side, an array of objects will be received and turned into client-side UserVO objects, and displayed in a DataGrid.

The new FlexLifeFacade.java class looks like this:

package test.flexlife;

import com.liferay.portal.model.User; import com.liferay.portal.service.UserLocalServiceUtil;

import java.util.List;

public class FlexLifeFacade { public UserVO[] getAllUsers() throws Exception { List<User> users = UserLocalServiceUtil.getUsers(0, 100); UserVO[] ret = new UserVO[users.size()]; int i = 0; for (User user : users) ret[i++] = new UserVO(user); return ret; } } }}} UserVO.java:

package test.flexlife;

import com.liferay.portal.model.User;

public class UserVO { public long userId; public String screenName; public String fullName; public String emailAddress;

public UserVO() {}

public UserVO(User user) { this.userId = user.getUserId(); this.screenName = user.getScreenName(); this.fullName = user.getFullName(); this.emailAddress = user.getEmailAddress(); } } }}}

On the client side, we have a counterpart UserVO class:

package test.flexlife
{ [RemoteClass(alias="test.flexlife.UserVO")] public class UserVO { public var userId:uint; public var screenName:String; public var fullName:String; public var emailAddress:String; } } }}}

And the Flex UI becomes:

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
applicationComplete="ro.getAllUsers()">

<mx:Script><![CDATA[ import mx.rpc.events.ResultEvent; private function result(e:ResultEvent):void{ grid.dataProvider = e.result as Array; } ]]></mx:Script>

<mx:RemoteObject id="ro" result="result(event)" endpoint="/messagebroker/amf" destination="FlexLifeServices"/>

<mx:DataGrid id="grid" width="100%" height="100%"> <mx:columns> <mx:DataGridColumn dataField="userId" headerText="User ID" /> <mx:DataGridColumn dataField="screenName" headerText="Screen Name" /> <mx:DataGridColumn dataField="fullName" headerText="Full Name" /> <mx:DataGridColumn dataField="emailAddress" headerText="Email" /> </mx:columns> </mx:DataGrid>

</mx:Application>}}}

Build and run. It looks like this:

Debugging Flex Applications In Liferay with HTTPS and Single Sign-On #

Normally, Flex programs are deployed to a server and are self contained with their own login screens. They do call the server-side, so the Flex executable must be downloaded from the server. To debug such Flex programs, in the Flex Builder, select Run|Debug|Other menu options, and set the URLs like so:

When debugging Flex applications running on Liferay, it can be tricky. Most likely your Flex program will call the server-side, and user authentication is required. In a normal flow of usage, a user logs onto Liferay, then the Flex application is launched; naturally, the user would not expect to login again into the Flex application. But this poses a dilemma for our debugging. If we still use the normal remote debugging of Flex, the “user” is not logged in, thus, many calls will fail; if we login to Liferay and navigate to the Flex wrapper HTML, how to attach the Flex debugger?

The solution for this dilemma is to silently fake a login user for the URLs of the Flex resources. Of course, this can only happen in the development environment. We will create a filter, which always sticks in the same user information into the session, so no explicit login is needed.

package test.flexlife;

import java.io.IOException; import javax.servlet.; import javax.servlet.http.; import com.liferay.portal.util.WebKeys;

public class MyDebugFilter implements Filter { private String screenName = ""; private Long userId = null;

public void init(FilterConfig fc) { screenName = fc.getInitParameter("screenName"); userId = Long.valueOf(fc.getInitParameter("userId")); }

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpreq = (HttpServletRequest) req; HttpSession session = httpreq.getSession();

session.setAttribute(WebKeys.USER_ID, userId);

chain.doFilter(req, res); }

public void destroy() {} } }}}

In your environment, your custom login handler may put more values into the session. You can fake them here as well just like we fake the userId.

Let's install this filter. In ext\ext-web\docroot\WEB-INF\web.xml, add these lines:

<filter>
<filter-name>My Debug Filter</filter-name> <filter-class>test.flexlife.MyDebugFilter</filter-class> <init-param> <param-name>screenName</param-name> <param-value>test</param-value> </init-param> <init-param> <param-name>userId</param-name> <param-value>10134</param-value> </init-param> </filter>

<filter-mapping> <filter-name>My Debug Filter</filter-name> <url-pattern>/flexlife/</url-pattern> </filter-mapping>}}}

How do you figure out the user information? Run this query against the Liferay database: SELECT userid, screenname from User_;

Ok, you get the idea, and the code...

Security Considerations of Flex Applications in Liferay #

Security Concerns #

It is a good practice to put security checks in the wrapper HTMLs and links. For instance, based on the role of the user, you may not even display the link leading to your Flex service. Or the Flex wrapper can be a JSP that checks the user role and optionally display a message rather than the Flash player. But to hackers, the URL to the Flex executable SWF file is readily available. Your Flex program on Liferay needs to implement security checks.

Enforcing Security Check in Flex and Server Calls #

Some high-level mission-critical Liferay APIs do check on the user permissions before carrying out the operations. In Liferay, when a user is logged in, a user-id is stored in the HTTP session. By getting this user and setting its permission checkers, you can use many of the Liferay APIs. The following is a useful helper method to invoke before calling such Liferay APIs:

import com.liferay.portal.model.User;
import com.liferay.portal.service.UserLocalServiceUtil; import com.liferay.portal.security.auth.PrincipalThreadLocal; import com.liferay.portal.security.permission.PermissionChecker; import com.liferay.portal.security.permission.PermissionThreadLocal; import com.liferay.portal.security.permission.PermissionCheckerFactory; import com.liferay.portal.util.WebKeys;

import flex.messaging.FlexContext;

import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession;

/ @return the login user of the session. Sets the permission checker if need to./protected User getCurrentUser() throws Exception {HttpServletRequest request = FlexContext.getHttpRequest();HttpSession session = request.getSession();Number userId = (Number)session.getAttribute(WebKeys.USER_ID);User user = UserLocalServiceUtil.getUserById(userId.longValue());

if (PrincipalThreadLocal.getName() == null) { String name = String.valueOf(userId); PrincipalThreadLocal.setName(name); PermissionChecker pc = PermissionCheckerFactory.create(user, true); PermissionThreadLocal.setPermissionChecker(pc); }

return user; } }}}

Lower-level Liferay APIs may not enforce user permission checks. When you make such calls, you need to programmatically enforce the user permissions.

Cross-Domain Flex Applications #

Flex applications in Liferay portal pages can be served from other domains as well. These Flex applications should not require users to log in again, so a single-sign-on infrastructure must exist across the Liferay server and the server servicing Flex applications. This is beyond the scope of this article.

Conclusions #

This article explains the discrepancy of RIAs technologies and JSR-168 portals, and why special care must be given to accommodate Flex applications in Liferay portal pages. It shows how to use and develop Flex applications on Liferay, and gives detailed steps to develop, debug, and host Flex applications in Liferay portal pages.

If you have successfully done all these steps, you are well equipped for full-blown Flex development with Liferay. Congratulations and have fun!

4 Allegati
44915 Visualizzazioni
Media (0 Voti)
La media del punteggio è 0.0 stelle su 5.
Commenti
Commenti Autore Data
Thanks, Ray, for adding the note regarding... James J.B. Huang 19 settembre 2009 8.15
Thanks for this. This is very helpful followed... Steve Reiner 18 settembre 2009 2.21
UserLocalServiceUtil.getUsers(0, 100); worked... Steve Reiner 18 settembre 2009 22.24
Thank you so much, Steve, for actually trying... James J.B. Huang 19 settembre 2009 8.47
FlexibleLiferay: Flex+AIR Portal Container for... Steve Reiner 4 novembre 2009 11.13
For developing Flex based portlets using... Mohammed Khan 9 novembre 2009 22.28
the wrapper HTML file is... Mohammad Danish 23 luglio 2012 23.12

Thanks, Ray, for adding the note regarding discrepency between RIAs and JSR-1/268 portals.

RIAs and page-based web applications are incompatible by nature. JSR-1/268 portals, being a page-based solution, is no exception. You can device all sorts of clever workarounds, but they can never be natural. Plus, since JSR-168 portlets are prevalent even with the default Liferay install, this page-refresh concern for RIAs in portals is not moot at all, but very very real.

Like I said in the article, Liferay has its values, and RIAs have their own. If they could be united seamlessly, great! If not, no shame either. Let's not start a religious war on this. emoticon
Inviato il 19/09/09 8.15.
Thanks for this. This is very helpful
followed all the steps, basic blazeds working, but with the liferay api example ro.getAllUsers() gives
[RPC Fault faultString="com.liferay.portal.SystemException : org.hibernate.HibernateException: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here" faultCode="Server.Processing" faultDetail="null"]
at mx.rpc::AbstractInvoker/http://www.adobe.com/2006/flex/mx/internal::faultHandler­()[C:\autobuild\3.2.0\frameworks\projects\rpc\src\mx\rpc\AbstractInvoker.as:220]­
(note this happens even when just running after login, know you need the filter if run flexbuilder debugger)
(any steps to avoid this? using liferay 5.2.3, blazeds 3.2, tried both tomcat 5.5 and 6.0, tried with the debug filter, tried with some of the additional tomcat authentication config in the blazeds 3.2 install instructions)
Some notes:
1. [RemoteClass(alias="test.flexlif.UserVO")] should be test.flexlife.UserVO
2. services-config.xml needs to have the [ ] around the url removed
3. one of the 2nd flex app need to have the [] around a namespace removed
4. MyDebugFilter needs some changes to compile
Inviato il 18/09/09 2.21.
UserLocalServiceUtil.getUsers(0, 100); worked without the exception that UserUtil.findAll();
gives. (UserLocalServiceUtil also has a getUsersCount() you could get all at once by giving (0, count-1) for start,end
Inviato il 18/09/09 22.24 in risposta a Steve Reiner.
Thank you so much, Steve, for actually trying out all these steps! You feedback is invaluable.

I've made minor changes to fix what is noted; unfortunately, the "[]"s are added around "http://...." by the wiki software, which is not good for source code listing.

That HibernateException is not expected, and I appreciate your workaround of using UserLocalServiceUtil. It's reasonable to have app code use service calls, so I just change the sample to use your code anyhow. (I used Liferay 5.2.2, and will try on 5.2.3; perhaps the convention for calling com.liferay.portal.service.persistence.*Util has been changed -- just my guess.)
Inviato il 19/09/09 8.47 in risposta a Steve Reiner.
FlexibleLiferay: Flex+AIR Portal Container for Liferay

Using these techniques I put together a Flex+AIR portal container for Liferay

http://code.google.com/p/flexible-liferay/
Inviato il 04/11/09 11.13 in risposta a James J.B. Huang.
For developing Flex based portlets using Liferay Plugin SDK check out this project http://code.google.com/p/esportlets .

Your feedback would be highly appreciated.
Inviato il 09/11/09 22.28.
the wrapper HTML file is webapps\ROOT\flexlife\flexlife.html
is wrong the correct path is webapps\flexlife\flexlife.html
for liferay 6.1
Inviato il 23/07/12 23.12 in risposta a Mohammed Khan.