Additional Blogs by Members
cancel
Showing results for 
Search instead for 
Did you mean: 
Former Member

In the part 1 of the series we have set up an EJB session facade with the Spring's CCI utility code for communicating with the ABAP back-end via JRA adapter deployed and configured on the AS. In this post we will look at the way we can deploy the web front-end of the application based on JSF 2.0 reference implementation. In addition, we will try to use a popular UI components library for JSF, Primefaces.

The reason for this (unusual) combination of technologies, is that NW 7.3 supports only JSF 1.2 out of the box, with a custom UI components library for the SAP look-and-feel. However, the JSF technology has greatly progressed since the JSF 1.2 edition, and the multitude very useful UI components libraries have been created for developing the UI with this technology. Here are some of them: Tomahawk, Richfaces, Primefaces, IceFaces, just to name a few. For our proof-of-concept I have chosen a popular combination of Mojarra JSF 2.0 implementation combined with Primefaces library.

Now available part 3 of the series. Source code is available on GitHub nw-jsf-showcase.

Deploying Mojarra to the AS

First we need to substitute the JSF 1.2 library used by default by the AS with the Mojarra 2.1.7 implementation of JSF 2.0. I'm reproducing below the steps described by Schindler Ingo in the forum post: JSF2 on Netweaver 7.3 (see the correct answer, and the remark at the end of the thread about "gzip-compression of the AS"), whose insight proved invaluable in this task.

Another very interesting article by Goran Stoiljkovski explains well how the AS resolves classpath dependencies and what is needed to deploy an application using so called heavy resource loaders. After several attempts, I settled on the Ingo's proposed method of deploying and referencing Mojarra as a library on the AS.

Download Mojarra library JAR. I chose javax.faces-2.1.7.jar, the latest at the time of the writing of this post. Create a simple General --> Project project in the NWDS workspace. Add the following files and folders to the project (Ingo's suggestion):

What we are doing here is creating by hand  the contents of the SDA file (mojarra217.sda) which will contain our JSF 2.0 library in order to deploy it to the AS. Later our application's EAR will reference this (deployed) library in it's application-j2ee-engine.xml descriptor. The easiest way to go about creating such SDA is to locate any library SDA already deployed on the AS, copy it locally, change the extension to .zip and unzip it. Then all the one needs to do is to adjust slightly the contents of the relevant files. Another way is to create the library SDA using the batch script make_SDA.csh found in <path_to_java_instance>/j2ee/deployment/scripts directory on the server. Look at ReadMe.txt for usage guidelines, and library.properties or primary-library.properties templates in the same directory.

Here are the contents of the SDA's files shown above:

application-j2ee-engine.xml

<application-j2ee-engine xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="application-j2ee-engine.xsd">
<provider-name>local.j2ee</provider-name>
</application-j2ee-engine>

Provider name is arbitrary, but you should be consistent throughout the SDA.

MANIFEST.MF

Manifest-Version: 1.0
Implementation-Title: javax~faces~2.1.7
Implementation-Version: 1
Implementation-Vendor-Id: local.j2ee
Specification-Vendor: SAP AG

Notice the Implementation-Title and Implementation-Vendor-Id entries.

SAP_MANIFEST.MF

Manifest-Version: 1.0
Ext-SDM-SDA-Comp-Version: 1
softwaretype: library
JarSAP-Standalone-Version: 20090803.1000
JarSAPProcessing-Version: 20090907.1000
deployfile: sda.xml
archivetype: DC
keyname: javax~faces~2.1.7
keyvendor: local.j2ee
keylocation: Deployment Manager
keycounter: 1
componentelement: <componentelement  name="javax~faces~2.1.7" vendor="local.j2ee" componenttype="DC" subsystem="NO_SUBSYS" location="Deployment Manager" counter="1" deltaversion="F" updateversion="LB-20120413181800" componentprovider="Deployment Manager" archivetype="DC"/>
JarSL-Version: 20100616.1800
compress: true

Notice the softwaretype, keyname entries and the name and vendor attributes in the componentelement.

sda-dd.xml

<SDA>
<SoftwareType>J2EE</SoftwareType>
<engine-deployment-descriptor version="2.0">
    <substitution-variable>
      <variable-name>com.sap.dc_name</variable-name>
    </substitution-variable>
    <substitution-variable>
      <variable-name>com.sap.dc_vendor</variable-name>
    </substitution-variable>
    <substitution-variable>
      <variable-name>com.sap.sld.GatewayHost</variable-name>
    </substitution-variable>
    <substitution-variable>
      <variable-name>com.sap.sld.GatewayService</variable-name>
    </substitution-variable>
</engine-deployment-descriptor>
</SDA>

sda.xml

<SDA>
    <SoftwareType>library</SoftwareType>
    <engine-deployment-descriptor version="2.0" />
</SDA>

Standard content for SDA deployment descriptor.

provider.xml

<!DOCTYPE provider-descriptor SYSTEM "library.provider.dtd">
<provider-descriptor>
    <display-name>javax~faces~2.1.7</display-name>
    <component-name>javax~faces~2.1.7</component-name>
    <major-version>2</major-version>
    <minor-version>1</minor-version>
    <micro-version>7</micro-version>
    <provider-name>local.j2ee</provider-name>
    <references>
        <reference type="library" strength="weak" provider-name="sap.com">servlet</reference>
    </references>
    <jars>
        <jar-name>lib/javax.faces-2.1.7.jar</jar-name>
    </jars>
</provider-descriptor>

Hint: one can find DTD files useful when creating some of these XML files by hand in <path_to_java_instance>/j2ee/cluster/server0/dtd directory on the AS.

Zip the project root directory and rename the resulting file with .sda extension (mojarra217.sda). You can now deploy the resulting SDA to the AS using Deploy View, External Deployable Archives.

Using the steps from Goran's article we can now check if the library was successfully deployed on the AS. Use SAP Management Console of NWDS to find out which is the port for the telnet on your AS, by looking at the Access Points, usually it is 50008. Login to the telnet using the administrator account. Use the command to list the library loaders of the AS (see man LL for help).

If you see your library listed with library: prefix then it was successfully deployed to the server.

Creating JSF 2.0 Dynamic Web Project

Now we are ready to create and deploy the web module of our application which will use the above Mojarra library and Primefaces UI componet library.

First we need to create a User Library in NWDS with the javax.faces-2.1.7.jar, this will be needed during JSF Facet selection of our Web project. For this, go to Preferences --> Java --> Build Path -->User Libraries and create new user library adding the Mojarra JAR to it.

Create new Dynamic Web Project. In the Configuration group, choose Default Configuration for SAP Libraries and click Modify button. Add JavaServer Faces 1.2 facet to the project (we will later change the JSF version by hand). Do not forget to add the Web project to the EAR project created in part 1 (tmp~sflight~ear).

When prompted to specify JSF capabilities, deselect the SAP Component Library for JSF and select the Mojarra user library you've created previously. This is needed only for compilation (access to javax.faces.context.FacesContext, etc), since at runtime it is the library deployed on the AS that will be used.

Once the project has been created it should have the following structure (with some of the files which will be added later) :

The project needs to reference the EJB and Java modules created in part 1. For this go to the project Properties --> Java EE Module Dependencies and select the EJB and Java modules. Now the Web module should have compile-time access to EJBs and DTOs.

We need to modify the web.xml file.

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    id="WebApp_ID" version="2.5">
    <display-name>tmp~sflight~web</display-name>
<!-- Configure listener for Spring web application context -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
<!-- Your default application web page, index.xhtml, for example, see example below -->
    <welcome-file-list>
        <welcome-file>layout.xhtml</welcome-file>
    </welcome-file-list>
<!-- Faces servlet and mapping of xhtml pages -->
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.xhtml</url-pattern>
    </servlet-mapping>
</web-app>

The only non-standard entry in this file is the reference to Spring's web application context loaded from the WEB-INF/applicationContext.xml. This file contains the jndi-lookup reference to the local stateless session EJB from the tmp~sflight~ejb project which will be deployed in the same EAR. We can find the exact JNDI name of the EJB using the technique described in part 1.

applicationContext.xml

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd">
    <!--
        Look up JraManagerBean EJB from the the JNDI   
     -->
    <jee:jndi-lookup id="jraManager"
        jndi-name="unil.ch/tmp~sflight~ear/LOCAL/JraManagerBean/ch.unil.sflight.ejb.JraManagerLocal" />
</beans>

We now modify faces-config.xml file to manually set the version of JSF to 2.0.

faces-config.xml

<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
    version="2.0">
<!-- application, converters, etc... -->
</faces-config>

We now are ready to create some views (xhtml pages) and corresponding JSF managed beans. For example, SearchFlights.java is shown below

//JSF 2.0 allows for annotation-driven specification of managed beans
@ManagedBean
@SessionScoped
public class SearchFlights implements Serializable {
    private static final long serialVersionUID = 1L;
    private static final Location loc = Location
            .getLocation(SearchFlights.class);
//we need to manually retrieve this bean from the application context, see init() method
    private transient JraManagerLocal jraManager;
    private String airlineId;
    private String destFrom;;
    private String destTo;
    private Collection<FlightData> flights;
//getters and setters are omitted
//will be executed once the bean has been initialized
    @SuppressWarnings("unused")
    @PostConstruct
    private void init() {
        jraManager = Utils.getBean("jraManager", JraManagerLocal.class);
    }
//action event called from the view
    public void search(ActionEvent event) {
        SimpleLogger
                .trace(
                        Severity.DEBUG,
                        loc,
                        "Search for flights with airlineId {0}, destFrom {1}, destTo {2}",
                        (Object) airlineId, (Object) destFrom, (Object) destTo);
        try {
            flights = jraManager.searchFlights(airlineId, destFrom, destTo);
            SimpleLogger.trace(Severity.DEBUG, loc, "Found {0} flights ",
                    (Object) flights.size());
            if (flights.size() == 0) {
                FacesContext.getCurrentInstance().addMessage(
                        null,
                        Utils.makeFacesMessage(FacesMessage.SEVERITY_INFO,
                                "info", "no_flights_found"));
            }
        } catch (BapiException e) {
            flights = null;
            Utils.processBapiErrors(e, loc);
        }
    }
}

The reference to the EJB has to be wired manually since we have chosen to let the JSF manage the scope of our beans, and so we cannot use @Autowired mechanism of Spring DI. You might be asking why didn't I just annotated the EJB reference with @EJB annotation, following the standard pattern. The problem is that when we deploy the EAR we will see the following error (which can be ignored).

Text:
May 2, 2012 3:20:32 PM com.sun.faces.spi.InjectionProviderFactory getProviderFromEntry
SEVERE: JSF1051: Service entry 'com.sap.faces.injection.InjectionProvider' does not extend DiscoverableInjectionProvider.  Entry will be ignored.

This error is expected since, the com.sap.faces.injection.InjectionProvider registered with Mojarra JSF 2.0 implementation does work with the default mechanism for EJB injection provided by the AS out of the box and configured for JSF 1.2. But to retrieve a been from the Spring's web application context is rather simple in the context of the JSF managed bean, we can use org.springframework.web.context.support.WebApplicationContextUtils for this purpose.

Utils.java

public class Utils {
    /**
     * Retrieves a bean with id given by <code>beanName</code> from current {@linkplain WebApplicationContext}.
     * @param <T> any type
     * @param beanName id of the bean
     * @param beanClass type of the bean
     * @return typed bean from the application context
     * @throws IllegalStateException if application context could not be retrieved
     * @throws BeansException if the bean with the given id could not be found
     * @throws ClassCastException if the retrieved bean cannot be cast to the provided type
     */
    public static <T> T getBean(String beanName, Class<T> beanClass) {
        WebApplicationContext appContext = WebApplicationContextUtils
                .getWebApplicationContext((ServletContext) FacesContext
                        .getCurrentInstance().getExternalContext().getContext());
        if (appContext == null) {
            throw new IllegalStateException(
                    "Cannot retrieve Spring web application context");
        }
        return beanClass.cast(appContext.getBean(beanName));
    }
}

Adding Primefaces UI component library

The real added value of all the work done above, comes with the ability to use third-party UI component libraries for JSF 2.0 framework. There is multitude of them, most of them are free or open-source and they allow for high quality, professional UI creation based on some leading edge client side scripting technology available. This not to mention the obvious advantages of using JSF 2.0 with it's improvements to navigation, AJAX support, and Facelets.

Note: Chris Hesse reported the issue with faces-config.xml validation to Primefaces. It should be fixed in 3.5.x and 4.0 branches, see the comments after this post.

To add Primefaces UI components library, you normally just need to add it to the set of Web App Libraries of the Web Project located in WEB-INF/lib directory. I've tried with primefaces-3.2.jar but got a following error during deployment.

Text:
[JLinEE reported following erros for unil.ch/tmp~sflight~ear application.
ERRORS:
* JSF Application Test: cvc-complex-type.2.4.a: Invalid content was found starting with element 'source-class'. One of '{"http://java.sun.com/xml/ns/javaee":system-event-listener-class}' is expected., file: tmp~sflight~web.war#META-INF/faces-config.xml, column 27, line 17

Apparently, the AS cannot validate the faces-config.xml packaged with the Primefaces JAR. But this is relatively easy to correct. You need to extract the contents of the primefaces-3.2.jar archive and edit the META-INF/faces-config.xml file. It need to be valid with respect to the declared javaee schema. This is just a matter of rearranging the order of some elements, for example, a system-event-listener element on the line 16 is

<system-event-listener>
    <source-class>javax.faces.application.Application</source-class>
    <system-event-class>javax.faces.event.PostConstructApplicationEvent</system-event-class>
    <system-event-listener-class>org.primefaces.webapp.PostConstructApplicationEventListener</system-event-listener-class>
</system-event-listener>

but should instead be

<system-event-listener>
    <system-event-listener-class>org.primefaces.webapp.PostConstructApplicationEventListener</system-event-listener-class>
    <system-event-class>javax.faces.event.PostConstructApplicationEvent</system-event-class>
    <source-class>javax.faces.application.Application</source-class>
</system-event-listener>

Once the validation errors are corrected, you can repackage the contents of the Primefaces JAR, which is now ready to be included as part of Web App Libraries of the project. To test if Primefaces are working you can use a simple index.xhtml page. If everything works well, you will be able to see the spinner UI component.

index.html

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:p="http://primefaces.org/ui">
    <h:head>
    </h:head>
    <h:body>
        <p:spinner />
    </h:body>
</html>

One more thing is needed for Primefaces to work on the AS. As Ingo mentions at the end of this forum post, we need to set the CompressedOthers property of HTTPProvider to false. Otherwise Primefaces will hung (until the connection timeout) trying to access some resources. For this login to /nwa interface with administrator, go to Configuration --> Infrastructure --> Java System Properties. Select Services tab and search for HTTP Provider. Set the CompressedOthers property to false (if needed). You might need to restart the AS for the changes to take effect.

Deploying the application

Before you deploy and run the application, you need to reference Mojarra JSF 2.0 library we deployed on the AS in the EAR's application-j2ee-engine.xml file.

<application-j2ee-engine>
    <!--
        This is the reference to the JSF 2.0 library,
        notice the prepend="true"
    -->
    <reference reference-type="hard" prepend="true">
        <reference-target provider-name="local.j2ee"
            target-type="library">javax~faces~2.1.7</reference-target>
    </reference>
    <!--
        This is only needed if you use com.sap.mw.jco.jra.ResultMap
        in tmp~sflight~java, see part 1
     -->
    <reference reference-type="hard">
        <reference-target target-type="application"
            provider-name="sap.com">tc~bl~jra~api</reference-target>
    </reference>
    <!--
        Cange to match your provider name
     -->
    <provider-name>unil.ch</provider-name>
</application-j2ee-engine>

At this point, it is likely that NWDS will complain that the XML file is invalid. You can disregard the error, but if you want to remove it, you can add an inline DTD declaration with prepend attribute specified as legal attribute of reference element.

<!DOCTYPE application-j2ee-engine [
<!ELEMENT application-j2ee-engine (reference*, classpath?, provider-name?, modules-additional?, fail-over-enable?)>
<!ELEMENT reference (reference-target)>
<!ATTLIST reference reference-type (hard|weak) #REQUIRED>
<!ATTLIST reference prepend (true|false) #IMPLIED>
<!ELEMENT reference-target (#PCDATA)>
<!ATTLIST reference-target target-type (application|library|service|interface) #REQUIRED>
<!ATTLIST reference-target provider-name CDATA #IMPLIED>
<!ELEMENT classpath (#PCDATA)>
<!ELEMENT provider-name (#PCDATA)>
<!ELEMENT modules-additional (module+)>
<!ELEMENT module (entry-name, container-type+)>
<!ELEMENT entry-name (#PCDATA)>
<!ELEMENT container-type (#PCDATA)>
<!ELEMENT fail-over-enable EMPTY>
<!ATTLIST fail-over-enable mode (disable|on_request|on_attribute) #REQUIRED>
]>

Once you've deployed the application, you should check the loaders for the application using telnet interface.

The reference to the library:local.j2ee~javax~faces~2.1.7 should be listed in the list of Direct Parent Loaders, and it should figure above the EAR's library loader, since we specified the prepend="true" in the application-j2ee-engine.xml file for this reference.

Summary of part 2

You should now have a working JSF 2.0 web application with Primefaces. You can use JQuery Themeroller to create a custom theme for the UI components of Primefaces which goes well with SAP look-and-feel. I'll try to add some screenshots of the application running on the AS portal soon.

18 Comments