Integrating a JSF application in Portal

Lately more and more customers have the need to integrate JSF / JHeadstart applications in a Portal environment. The standard way to do this is by using a JSF Portlet Bridge, which has some restrictions. This post describes an alternative solution that overcomes those restrictions by using inline frames.

Restrictions of the JSF Portlet Bridge

The JSF Portlet Bridge component of Oracle WebCenter executes Oracle ADF Faces pages as JPS (JSR 168) portlets using WSRP. Web Services for Remote Portlets (WSRP)
is a web services protocol for aggregating content and interactive web
applications from remote sources. After registration of the producer
this portlet can be used in the Oracle Portal environment. For this solution with WebCenter a license is necessary.

To publish an Oracle ADF Faces page as a portlet using the JSF Portlet Bridge, you must first code
your Oracle ADF Faces pages such that they emit markup that conforms
with JSR 168 portlet markup fragment rules. The guidelines that follow lay out the issues of which you should be
aware as you code Oracle ADF pages that you may later choose to publish
as portlets (see the WebCenter Developer's Guide, section 19.5.2.3 Oracle ADF Faces Guidelines):
  • Oracle ADF Faces client side implementation is not supported. One weakness of JSR 168 and WSRP 1.0 is that they do not account for portlets using client-side programming techniques, such as Ajax or hidden frames, for partial page rendering. As a result, all Oracle ADF Faces facilities that rely on client-side programming techniques do not work. These facilities include the following:
    • Oracle ADF Faces partial page refresh (PPR) facility
    • The client-rendered aspect of Oracle ADF Faces rich client components
    • Oracle ADF popups, <af:popup>, which fail to work because of their reliance on the PPR facility
    • The Oracle ADF Faces SelectText, SelectDate, and SelectColor components, which use visual assist popups
  • File upload is not supported. To support file upload, Oracle ADF Faces requires facilities that do not exist in JSR 168. Hence, a file upload request does not work.
  • The objectMedia component is not supported in portlets.
In addition, you need to be aware of the following general restrictions when using the JSF Portlet Bridge:
  • Such portlets do not share the HttpSession and ADF BindingContext (no intercommunication possible between portlets on a page)
  • Oracle Portal version 10.1.4 or higher is needed
The JSF Portlet bridge is therefore useful for read-only JSF pages, and for JSF pages that can process input without file upload, Lists of Values (LOV's), or date picker popups. As an alternative you could use custom components that have an inline date picker for example.

To integrate JSF applications in an Oracle Portal environment without having to deal with the Portal version or the limitations that JSR 168 portlets have, we've come up with an alternative solution that I will discuss in more detail below. This solution has been implemented at a customer for which the Portal version (10.1.4 or higher) and the restrictions mentioned above were unacceptable.

Technical Summary of Iframe Solution

The custom solution is based on iframes (HTML inline frames).
  • We include an iframe in a Portal page.
  • This iframe contains the JSF application that is exposed as a Portal page component (a 'portlet' if you will).
  • You can run the JSF application in a Portal page or stand-alone without the need to change the JSF code.
  • The page component will automatically resize to the size of the JSF page.
  • Parameters are passed through the iframe so that communication between Portal and the JSF application is possible.
  • The JSF applications that are exposed through these iframes share the same session so that communication between the JSF applications within different iframes is possible.
  • When deeplinks are used from one JSF page to another, and  the new JSF page must be shown together with other portlets (and JSF applications) on that page, the entire Portal page can be refreshed.
Because the application runs in an iframe like an ordinary stand-alone JSF application and not as a JSR 168 portlet, none of the aforementioned restrictions of the JSF Portlet Bridge apply to this solution. In addition, this solution can be used with all Oracle Portal versions. You could use this technique as an intermediate solution until the JSF Portlet Bridge offers all the functionality you need for your application (maybe if WSRP 2.0 / JSR 286 is supported).

Steps:
1. JSF Page in Iframe on Portal Page
2. Frame Resizing
3. Inter-Portlet Communication
4. Deeplinking

1. JSF page in Iframe on Portal page

1.1 Generating an Iframe in Portal

First of all we must create a custom item type in Portal that will generate our iframe (you could also create a portlet with an iframe). In the iframe we need to pass information that the JSF application needs in order to run within the Portal page. To do this we can supply URL parameters to the source attribute of the iframe tag that the application can extract and use.

The custom item type will call the following PL/SQL procedure to generate an iframe as its output:

procedure generate_iframe(p_source_url varchar2, p_width varchar2, p_height varchar2)
is
begin
  -- print the iframe tag with specified source, height and width
  htp.p('<IFRAME SRC="'||p_source_url||'?isinframe=true" WIDTH="'||p_width
    ||'" HEIGHT="'||p_height||'" frameborder="0" scrolling="no"></IFRAME>');
end;


The custom item type supplies the parameter values for p_source_url (the url to the JSF application), p_height and p_width. In the code we can see that a parameter 'isinframe' is passed to the application. In this code example only one parameter is passed, but naturally you can add more parameters when needed. The 'isinframe' parameter is an indicator for the JSF application that it runs in a Portal page. Our solution provides a way to either run the application in a Portal page or just stand-alone without the need of changes!

1.2 Creating a Session Bean to store passed parameters

To use the parameters that are passed to the JSF application we need to create a session bean (for example PortletContextBean) in the application that stores the information. This bean will have a local variable, a getter, and a setter for each parameter that is passed from the Portal to the JSF application.

1.3 Creating a Servlet Filter that sets the Session Bean

To handle the session bean you have to create a servlet filter that maintains the state of the PortletContextBean. First you have to configure a servlet filter (for example PortletFilter) in the web.xml:

...
<filter>
  <filter-name>PortletFilter</filter-name>
  <filter-class>[package path].PortletFilter</filter-class>
</filter>
...
<filter-mapping>
  <filter-name>PortletFilter</filter-name>
  <url-pattern>/faces/*</url-pattern>
</filter-mapping>
...


Next you have to implement the servlet filter:

public void doFilter(ServletRequest  servletRequest,
                     ServletResponse servletResponse,
                     FilterChain     filterChain)
throws IOException, ServletException
{
  HttpServletRequest httpRequest = (HttpServletRequest)servletRequest;
  // get the PortletContextBean from the session if it exists
  PortletContextBean portlet =
    (PortletContextBean)httpRequest.getSession().getAttribute("portletContextBean");
  // if the session bean does not exist create a new one
  if (portlet == null)
  {
    PortletContextBean bean = new PortletContextBean();
    // check if the parameter 'isinframe' exists
    if (httpRequest.getParameter("isinframe") != null)
    {
      // the JSF application runs in an iframe, so set the indicator
      // of the session bean to true
      bean.setInframe(true);
    }
    // store the session bean on the session
    httpRequest.getSession().setAttribute("portletContextBean", bean);
  }
}

1.4 Dynamically Switching between Stand-Alone and Portal Mode

In the example above we can see that an indicator 'isInframe' is stored that tells if the application runs stand-alone or within Portal. So by passing parameters via the iframe to the application and storing them in a session bean we can now use this information in the application.

Using the 'isInframe' indicator we can now conditionally render JSF elements of a page by using the rendered property. Typically we do not render the branding images, global buttons and menu items when running in an iframe. You have to add the following code to a JSF element that needs to be conditionally rendered:

rendered="#{!portletContextBean.inframe}"


It's very easy to generate the rendered property within the JSF elements where needed with custom JHeadstart Generator Templates. With our solution the application can run stand-alone or in an iframe without making code changes.

The following picture shows a section of a Portal page that contains a JSF application (employee overview). The pictures used in this blog are only to give you an idea of the end result. They contain unfortunately Dutch texts, for this I apologize. The left side of the picture shows a left navigation menu; this is a section of the Portal page. On the right, next to the navigation menu, there is an iframe containing the JSF page (employee overview table with search functionality).

Employee overview portal:

2. Frame Resizing

2.1 Adding a Javascript Function to Adjust the Iframe Size

In order to automatically resize an iframe to the content height of the JSF page we have to add/include the following javascript on the Portal pages. The JSF page in the iframe will call this javascript on the Portal page.

<SCRIPT TYPE="text/javascript">
  function adjustIFrameSize(h,name)
  {
    var iframeElement = document.getElementById(name);
    iframeElement.style.height = h + 10 + 'px';
  }
</SCRIPT>


If a JSF application runs in an iframe the height of that iframe has to be adjusted to the content height of that page. In that case the method adjustIFrameSize is called in the onload event of that page.

2.2 Passing the Iframe id to the JSF Page

We also have to supply the iframe id to the JSF page so that it knows in which iframe it runs.

procedure generate_iframe(p_source_url varchar2, p_width varchar2, p_height varchar2
  , p_id varchar2)
is
begin
  -- print the iframe tag with specified source, height and width
  htp.p('<IFRAME SRC="'||p_source_url||'?isinframe=true&frameId='||p_id
    ||'
" WIDTH="'||p_width||'" HEIGHT="'||p_height
    ||'" id="'||p_id||'" frameborder="0" scrolling="no"></IFRAME>');
end;

2.3 Dynamically Adding the Javascript for the Onload Event

Next we add a method in the PortletContextBean that generates the onload event. This enables us to only add the onload Javascript if the page is rendered in the Portal, and it also allows us to dynamically add extra code later on (see 3. Inter-Portlet Communication).

...
public String getOnload()
{
  // the frame id of the iframe in which the JSF page runs
  String frameId = JsfUtils.getHTTPServletRequest().getParameter("frameId");
  StringBuffer onload = new StringBuffer();
  if (isInframe())
  {
    // append the javascript that resizes the iframe to the height of
    // the JSF page
    onload.append
      ("parent.adjustIFrameSize(document.getElementById('body').scrollHeight,'"
      +frameId+"');");
  }
  return onload.toString();
}
...

2.4 Calling the Onload Event

Now we have to add the onload event to every page. This can easily be done using JHeadstart templates.

...
<afh:body id="body" onload="#{portletContextBean.onload}">
  <af:form ...>
    <af:inputHidden id="frameId" value="#{param.frameId}"/>
...


The iframe id that is passed through the iframe as parameter is used by the application to identify in which iframe it runs, so that it can resize the right iframe. We add a hidden parameter that contains the iframe id so that it gets passed from page to page in the flow of an iframe.

2.5 Resizing in Case of Partial Page Rendering

ADF Faces (Oracle's JSF components) offers the feature of Partial Page Rendering, which allows the browser to render a piece of a page instead of the entire page. We must use another technique to resize the iframe in case of a partial page request, because partial page rendering will not reload the page and as such will not invoke our onload event. For the components that use partial page rendering and influence the height of the page (for example detail disclosure / inline overflow in a table or switching between quick and advanced search), you have to include the following code, and put the component's id in the partialTriggers property:

<afh:script text="parent.adjustIFrameSize(document.getElementById('body').scrollHeight,'#{param.frameId}');" partialTriggers="......." rendered="#{portletContextBean.inframe}"/>

The following picture shows the same Portal page section as the previous picture, but now the detail disclosure is unfolded and the iframe was automatically resized.

Employee overview detail portal:

An alternative to resizing the iframe is to use scrollbars in the iframe. You can specify this in the iframe tag (change scrolling="no" to scrolling="yes").

3. Inter-Portlet Communication

So far we have discussed a single iframe containing a JSF application on a Portal page, but what if we want to have multiple JSF applications/iframes on a Portal page? And what about communication between these applications?

Let's take a look at the following example:

We have a Portal page on which customers can edit their employees. Every change (transaction) made by a customers is placed in a shopping cart, so that the customer can do or undo multiple transactions before finalizing them. In another region of the page there is an overview of the number of transactions in the shopping cart. To construct this page we have two iframes in different Portal regions, one containing a JSF page that displays the number of changes in the shopping cart and one containing a JSF page with the functionality of searching for employees and editing them. When a button is pressed that places a change in the shopping cart, the iframe that contains the shopping cart overview must be reloaded in order to display the correct number of items in the shopping cart.
The following picture shows a section of a Portal page that contains the edit employee JSF page as well as the shopping cart in the left navigation bar (two iframes are used here). No previous transactions were made so all of the counters are 0.

Reload other iframes 1:

The next picture shows the Portal page after the change was made. The counter of the shopping cart is now set to 1. This all has been done without the need of refreshing the entire Portal page.

Reload other iframes 2:

The following steps explain how this can be achieved. Note that if you implemented the iframe in a portlet instead of an item type, you could also use portlet events.

3.1 Adding a Javascript Function to Reload Other Iframes

First we have to include javascript on the Portal page that is able to reload all of the iframes on that page except the one from which the javascript call originated.

<script type="text/javascript">
  function reloadIFrames(frameId)
  {
    var frame;
    var frames = document.getElementsByTagName('iframe');
    for (var i = 0; i < frames.length; i++)
    {
      frame = frames[i];
      //only reload the other iframes
      if (frame.id != frameId)
      {
        frame.src="frame.src;
     " }
    }
  }
</script>

3.2 Notifying the Need for Reload by Setting a Flag in the Session Bean

Secondly, in the case that the 'button pressed' event occurs, we have to set a flag (reloadPortalPage) in our PortletContextBean that indicates that the other iframes must be reloaded.

<af:commandButton ... >
  <af:setActionListener from="#{true}" to="#{portletContextBean.reloadPortalPage}"/>
</af:commandButton>

3.3 If the Reload Flag is Set, Adjust the Onload Javascript

Finally, we have to adjust the getOnload() method in our PortletContextBean.

...
public String getOnload()
{
  // the frame id of the iframe in which the JSF page runs
  String frameId = JsfUtils.getHTTPServletRequest().getParameter("frameId");
  StringBuffer onload = new StringBuffer();
  if (isInframe())
  {
    // append the javascript that resizes the iframe to the height of
    // the JSF page
    onload.append
      ("parent.adjustIFrameSize(document.getElementById('body').scrollHeight,'"
      +frameId+"');");
    // only append javascript for reloading iframes
    // when a 'button pressed' event has occurred
    if (isReloadPortalPage())
    {
      setReloadPortalPage(false);
      onload.append("parent.reloadIFrames('"+ frameId+"');");
    }

  }
  return onload.toString();
}
...


Now, the onload event will not only resize the iframe but it will also reload all of the other iframes when needed.

The example above is not possible when using two JSF portlets created with the JSF Portlet Bridge instead of the two iframes, because they do not share the same HttpSession. JSF applications in an iframe however have their own HttpRequest, but they share the HttpSession and ADF BindingContext. This makes it possible to not only exchange information with request parameters, but also through session attributes.

4. Deeplinking

This section will discuss deeplink integration in Portal. Let's take a look at the following example:

We use Portal to maintain and manage our content instead of adding the content to JSF pages. For a single Portal page this include help text for applications that run on that page, so we can manage help text and content of a page without having to rebuild and redeploy our JSF application.

Let's say we have a page containing an overview of transactions that also contains additional information for these transactions. We now want to be able to directly select one of these transactions and modify it, so we add a deeplink for each transaction. If we run this application in an iframe and we use the deeplink, the correct 'modify transaction' JSF page will be loaded but in the same iframe. So we have the new JSF page, but in the same Portal page with the same context/content. What we want is that the deeplink jumps to a different Portal page that contains the 'modify transaction' application with corresponding context (for example help text for the modification page).

The following picture shows a section of a Portal page with an overview of the transactions in table layout. On the left side we can still see the shopping cart counter. The link 'Wijzigen' is the deeplink to the Portal page that contains the 'modify transaction' application.

Transaction overview:

If we click on the deeplink we will be redirected to the Portal page with the 'modify transaction' application, displaying the relevant row; the following picture represents a section of this page.

Edit gebruikers:

In order to integrate deeplinking in Portal the following steps have to be made.

4.1 Passing the Deeplink Parameters through the Iframe

We must pass two pieces of information when performing a deeplink:
  1. The URL of the target page (the deeplink URL)
  2. The id of the target row that must be displayed on that page (the deeplink id)
The following code must be added to the generate_iframe procedure:

procedure generate_iframe(p_source_url varchar2, p_width varchar2, p_height varchar2
  , p_id varchar2, p_deeplink_url varchar2 default null)
is
  l_deeplink_id  varchar2(32000);
begin
  l_deeplink_id := wwpro_api_parameters.get_value
                     ( p_name => 'deeplinkid'
                     , p_reference_path => 'a');

  -- print the iframe tag with specified source, height and width
  htp.p('<IFRAME SRC="'||p_source_url||'?isinframe=true&deeplinkid='||l_deeplink_id
    ||'
&frameId='||p_id||'&deeplinkpage='||p_deeplink_url
    ||'" WIDTH="'||p_width||'" HEIGHT="'||p_height
    ||'" id="'||p_id||'" frameborder="0" scrolling="no"></IFRAME>');
end;


The deeplinkid request parameter is passed by the source JSF page to the target Portal page. The PL/SQL code extracts this parameter and passes it on to the target JSF page. The p_deeplink_url PL/SQL parameter is supplied by the iframe custom item type (so you have to adjust the custom item type as well); it contains the target Portal page url that the JSF application can use as deeplink url.

4.2 Storing the Deeplink URL in the Session Bean

Next, the deeplinkpage request parameter must be stored in the PortletContextBean. So we have to adjust the servlet filter code.

public void doFilter(ServletRequest  servletRequest,
                     ServletResponse servletResponse,
                     FilterChain     filterChain)
  throws IOException, ServletException
{
  HttpServletRequest httpRequest = (HttpServletRequest)servletRequest;
  // get the PortletContextBean from the session if it exists
  PortletContextBean portlet =
    (PortletContextBean)httpRequest.getSession().getAttribute("portletContextBean");
  // Get deeplink page url
  String deeplinkurl = httpRequest.getParameter("deeplinkpage");

  // if the session bean does not exist create a new one
  if (portlet == null)
  {
    PortletContextBean bean = new PortletContextBean();
    // check if the parameter 'isinframe' exists
    if (httpRequest.getParameter("isinframe") != null)
    {
      // the JSF application runs in an iframe, so set the indicator
      // of the session bean to true
      bean.setInframe(true);
    }
    if (deeplinkurl != null)
    {
      // a deeplink page url is passed by the iframe
      bean.setDeeplinkUrl(deeplinkurl);
    }

    // store the session bean on the session
    httpRequest.getSession().setAttribute("portletContextBean", bean);
  }
  else // session bean already exists
  {
    if (deeplinkurl != null)
    {
      // a deeplink page url is passed by the iframe
      // store the deeplink url and set the adjusted session bean on the session
      portlet.setDeeplinkUrl(deeplinkurl);
      httpRequest.getSession().removeAttribute("portletContextBean");
      httpRequest.getSession().setAttribute("portletContextBean", portlet);
    }
  }

}

4.3 Dynamically Switching between Stand-Alone Deeplink and Portal Deeplink

We can now use the Portal page url as a deeplink in a JSF page.

<af:commandLink immediate="true" action="..." text="..."
rendered="#{!portletContextBean.inframe}">
  <f:param name="deeplinkid" value="#{...}"/>
</af:commandLink>
<af:goLink text="..." targetFrame="_parent"
rendered="#{portletContextBean.inframe}"destination="#{portletBean.deeplinkUrl}&deeplinkid=#{...}"/>


The commandlink is shown when running stand-alone and the golink when running within an iframe. The destination of the golink is a deeplink URL (Portal page) stored in the portletContextBean. The deeplink id (that identifies the row that must be displayed on the target page) is added as parameter of the Portal page URL and passed via the iframe on the destination page to the JSF page (see PL/SQL procedure above).

Conclusion

With these techniques you can now integrate JSF applications into your Portal environment without the current limitations of the JSF Portlet Bridge and at the same time you can run these JSF applications stand-alone without the need of changing code!

Comments:

Hi, I have used the idea given in this blog in my application. But the main issue with this approach is the Security. We just pass url and parameters. But no security check is done on the JSF application side about the valid session. Please correct me if I am wrong. Regards, Deepak Suri

Posted by Deepak Suri on November 19, 2007 at 02:52 AM PST #

Hi,

In my environment we used SSO for authentication, so the Portal page and also the web application will require a valid logged in user. In the JSF application we just use JAAS for authentication/authorization (configured to use SSO). In a servlet filter we store the logged in user in the java context and then in a next request we can check that the logged in user is the same as the stored user, so we have the same SSO session. If not we invalidate the java session.

I hope this makes it clear.

Robert Korndewal

Posted by Robert Korndewal on November 22, 2007 at 09:19 PM PST #

Post a Comment:
  • HTML Syntax: NOT allowed
About

Java EE Consultants - JHeadstart, ADF, JSF

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
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
   
       
Today