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

This is the first of a series of posts describing a proof-of-concept application for NetWeaver 7.3 JEE application server using some popular Java development frameworks: Spring, JSF 2.0, and Primefaces. It is not intended to be used as is for any practical purpose, but instead can serve as a guide to using these frameworks on the SAP Java AS. The application uses Spring CCI temlpate to communicate with the back-end via JRA adapter, with the RFC calls executed within EJB session facade (thus allowing for the container-managed transactions), and with the user interface implemented as JSF 2.0 (Mojarra 2.1.7) web application with Primefaces. The application uses the standard SAP demo package for flight booking on the ABAP back-end.

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

Requirements for the implementation of the application:

  • Administrator access to the NetWeaver administrator interface of AS
  • SSH connection to AS (sfp, telnet)
  • Back-end user capable to execute BAPI calls and working knowledge of SAP GUI client (SE37)
  • Familiarity with NetWeaver Developer Studio (EAR, EJBs, Web)
  • Working knowledge of Spring framework and JSF 2.0

Setting up JCA factory using JRA resource adapter

We need to make sure that we have the relevant flight data in the back-end tables. If necessary, the sample data for the flights can be generated on the back-end with SAPBC_DATA_GENERATOR program, which can be launched via SE38 transaction.


Once we have the data, we need to set-up the communication with the back-end. For this we will use the Java Resource Adapter deployed on the AS.

NW 7.3 has a JRA adapter deployed by default. What we need is to set-up a JCA connection factory (using the JRA resource adapter) which will be later looked up from the JNDI and used to create CCI connections to the back-end.

To create the JCA connection factory login to /nwa as the administrator, go to Configuration --> Infrastructure --> Application Resources. Search and select SAPJavaResourceAdapter15 entry. In the Resource Details pane, select Related JCA Resource and click on JCA Resource Details button.

Select eis/SAPJRAFactory and click on Copy and Add New Connection Factory. Chose an appropriate factory name (for example, eis/BAS_SAPJRAFactory, where BAS is for the system ID of the back-end sever). We now need to configure the connection properties for the factory we just created. Enter connection pooling properties which determine the maximum number of connections to be created by this factory and the maximum idle time for each connection.

In the configuration properties we enter the parameters relevant to the back-end server: username, password, client number, which will be used in the JCo connections used by JRA.

Java module with Spring CCI support

We now are ready to create our application in NWDS. Normally, the application should be a collection of Development Components (DCs) taking advantage of NetWeaver DI, but for the sake of simplicity we will create a standalone EAR application. Create a simple Java Project (tmp~sflight~java). Then create an Enterprise Application Project (tmp~sflight~ear). Leave default target runtime (SAP Libraries), EAR version (5.0), and configuration (Default Configuration for SAP Libraries). Important: when prompted, select tmp~sflight~java as Java EE module dependencies, this will add all the necessary SAP JEE libraries as well as the EAR libraries from /tmp~sflight~ear/EarContent/lib directory to the classpath of the Java project.

We need to add Spring compile and runtime dependencies to our EAR project. Use your preferred way to get them. I used Apache Ivy to get all the dependencies for the org.springframework:spring-context:3.1.RELEASE version. Add these libraries to /tmp~sflight~ear/EarContent/lib directory, this way they will be deployed with our application archive and will be automatically available on the classpath of our Java, EJB, and Web modules, as well. The EAR project setup should look as following:

And the Java project as follows:

Notice that there is one more library (sap.com~tc~bl~jra~RUNTIME.jar) which I added to the build path of the Java project manually. This is needed optionally to be able to take advantage of the SAP's com.sap.mw.jco.jra.ResultMap extension of javax.resource.cci.MappedRecord interface. The library for this API is normally configured as a dependency to the api public part of the tc/bl/jra/api DC of ENGFACADE SC, but since we are not using DCs, we can use the equivalent library found on the AS itself (just login with SSH and run locate tc~bl~jra to see where exactly it is located). Alternatively, you can create a simple DC with this dependency and get the jar file by browsing the workspace .jdi directory once the DC is built. The rest of this post does not have any references to this API.

The idea is to be able to use JCA CCI support of the Spring library in order to greatly facilitate the interaction with the JRA adapter, managing connections, executing BAPI calls, etc.

The following will explain how we can call BAPI_FLIGHT_GETLIST BAPI in the back-end and get the list of the flights by the airline ID. Create a simple Java DTO object which will contain flight information.

public class FlightData implements Serializable {
    private String airlineId;
    private String airlineName;
    private String connectId;
    private Date flightDate;
    private String airportFrom;
    private String cityFrom;
    private String airportTo;
    private String cityTo;
    private Time departureTime;
    private Time arrivalTime;
    private Date arrivalDate;
    private BigDecimal price;
    private String currency;
//getters and setters
}

This corresponds to the data object BAPISFLDAT used by ABAP for the FLIGHT_LIST output table for this BAPI.

Then we need an extension of org.springframework.jca.cci.object.MappingRecordOperation as follows:

public class BapiFlightGetListOperation extends
        MappingRecordOperation {
//Location object for SAP logger
private static final Location loc = Location.getLocation(BapiFlightGetListOperation.class);
    public BapiFlightGetListOperation(ConnectionFactory connectionFactory) {
        super(connectionFactory, null);
    }
    @SuppressWarnings("unchecked")
    @Override
    protected Record createInputRecord(RecordFactory recordFactory, Object obj)
            throws ResourceException, DataAccessException {
        Object[] input = (Object[]) obj;
        SimpleLogger.trace(Severity.DEBUG, loc, "Record inputs: "
                + Arrays.toString(input));
        MappedRecord record = recordFactory
                .createMappedRecord("BAPI_FLIGHT_GETLIST");
        // TODO: set other input parameters
        if (input[0] != null) {
            record.put("AIRLINE", ((String) input[0]).toUpperCase());
        }
        return record;
    }
    @Override
    protected Object extractOutputData(Record record) throws ResourceException,
            SQLException, DataAccessException {
        Object obj = null;
     //custom method to see if BAPI returned with errors
        Bapiret2[] errors = checkForBapiErrors(((MappedRecord) record),
                "RETURN");
        if (errors.length == 0) {
            ArrayList<FlightData> flights = new ArrayList<FlightData>();
            ResultSet cursor = (ResultSet) ((MappedRecord) record)
                    .get("FLIGHT_LIST");
            while (cursor.next()) {
                flights.add(readFlightData(cursor));
            }
            obj = flights;
        } else {
            obj = errors;
        }
        return obj;
    }
private Bapiret2[] checkForBapiErrors(MappedRecord record,
            String bapiretTableName) throws SQLException, DataAccessException {
//omitted for clarity
    }
    private FlightData readFlightData(ResultSet cursor) throws SQLException {
        FlightData data = new FlightData();
        data.setAirlineId(cursor.getString("AIRLINEID"));
        data.setAirlineName(cursor.getString("AIRLINE"));
        data.setConnectId(cursor.getString("CONNECTID"));
        data.setFlightDate(cursor.getDate("FLIGHTDATE"));
        data.setAirportFrom(cursor.getString("AIRPORTFR"));
        data.setCityFrom(cursor.getString("CITYFROM"));
        data.setAirportTo(cursor.getString("AIRPORTTO"));
        data.setCityTo(cursor.getString("CITYTO"));
        data.setDepartureTime(cursor.getTime("DEPTIME"));
        data.setArrivalTime(cursor.getTime("ARRTIME"));
        data.setArrivalDate(cursor.getDate("ARRDATE"));
        data.setPrice(cursor.getBigDecimal("PRICE"));
        data.setCurrency(cursor.getString("CURR"));
        return data;
    }
}

This implementation will create an instance of Spring's MappingRecordOperation class using passed javax.resource.cci.ConnectionFactory instance. The implementation of the method createInputRecord() is responsible for specifying BAPI's input values. And the method extractOutputData(), for extracting the output and storing it in the DTO object. Finally, here is the extension of org.springframework.jca.cci.core.support.CciDaoSupport which is responsible for creating the instance of BapiFlightGetListOperation and executing the BAPI with actual parameters.

public class JraDao extends CciDaoSupport {
      //custom extension of MappingRecordOperation
    private BapiFlightGetListOperation flightGetListOp;
     //connecionFactory will be injected via DI, see context configuration
    @Override
    protected CciTemplate createCciTemplate(ConnectionFactory connectionFactory) {
        CciTemplate cciTemplate = super.createCciTemplate(connectionFactory);
        flightGetListOp = new BapiFlightGetListOperation(connectionFactory);
        return cciTemplate;
    }
     //executes MappingRecordOperation with actual parameters
    public Object searchFlights(String airlineId,
            String destFrom, String destTo) {
        return flightGetListOp.execute( new Object[]{airlineId, destFrom, destTo});
    }
}

All of this Spring magic comes together of course through application context configuration file. The following section deals with the configuration of the EJB project which will serve as a session facade to the JSF webapp, and which is responsible for instantiation of org.springframework.context.support.ClassPathXmlApplicationContext needed for the communication layer to work.

EJB project and common application context

Create an EJB Project (tmp~sflight~ejb) leaving all default options, do not forget to add it to the tmp~sflight~ear EAR project as JEE module. Also specify a reference to tmp~sflight~java Java project so that the EJBs have access to the DTOs.

The idea is to be able to delegate to Spring the creation of the JraDao instance (described above) with an instance of JCA connection factory looked up from JNDI of the AS and inject it into our session bean. We can use Spring's EJB 3 Injection interceptor which automatically instantiates the beans found in the special context file, beanRefContext.xml (available on the classpath, see Java project setup).

<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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--
        Specify the list of application contexts to be loaded and shared by all
        the WAR and EJB modules in the EAR. All the beans from these contexts
        can be made available to the EJBs via
        @Interceptors(SpringBeanAutowiringInterceptor.class) class level annotation.
        To reference the beans from these contexts in a WAR module use
        usual Spring mechanism for bootstrapping web application context with
        "parentContextKey" context parameter set to "earContext"
        (value of the bean's "id" attribute below.)
     -->
    <bean id="earContext"
        class="org.springframework.context.support.ClassPathXmlApplicationContext">
        <constructor-arg>
            <list>
                <value>common-context.xml</value>
            </list>
        </constructor-arg>
    </bean>
</beans>

The beans declared in the context file common-context.xml (from Java project) are then not only available to be injected into the EJBs but also can be referenced from WAR web application context of the same EAR, as well. Here are the contents of the common-context.xml file :

<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">
    <!--
        Configure logging for Spring classes with Log4j logger and
        a custom appender for SAP com.sap.tc.logging.Location.
        For this to work log4j.properties file needs to be packaged
        with the EAR module together with the Spring's libraries
        and the custom appender implementation.
     -->
    <bean
        class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"
        p:targetClass="org.springframework.util.Log4jConfigurer"
        p:targetMethod="initLogging">
        <property name="arguments">
            <list>
                <value>classpath:log4j.properties</value>
            </list>
        </property>
    </bean>
    <!--
        Look up JRA adapter bean from JNDI registry on the AS.
     -->
    <jee:jndi-lookup id="eisSapJraConnectionFactory"
        jndi-name="deployedAdapters/eis/BAS_SAPJRAFactory/shareable/eis/BAS_SAPJRAFactory" />
    <!--
        DAO object using CCI API to work with JRA adapter.
        If no reference to ConnectionFactory is specified then
        MappingRecordOperation references should be wired
        manually.
     -->
    <bean id="jraDao" class="ch.unil.sflight.java.jra.JraDao"
        p:connectionFactory-ref="eisSapJraConnectionFactory" />
    <!--
        Autowire Spring dependencies into EJBs.
     -->
    <context:component-scan base-package="ch.unil.sflight.ejb" />
</beans>

Note that we are using jee:jndi-lookup tag as a convenient way to lookup the JCA factory from the JNDI context. The JNDI name of the factory can be found as following. Login to /nwa as the administrator, go to Troubleshooting --> JNDI Browser. Search for the object with the same name as the one used when configuring JCA connection factory (see above).

Select the shareable resource, the Object Name is the JNDI name which should be specified in the jee:jndi-lookup. Using Spring's context:component-scan mechanism, we can now autowire the JraDao instance into our session bean.

//annotation needed to bootstrap context from beanRefContext.xml
@Interceptors(SpringBeanAutowiringInterceptor.class)
@Stateless
//annotation needed for context:component-scan to work
@Component
public class JraManagerBean implements JraManagerLocal {
     //autowired by context:component-scan
    @Autowired
    private JraDao jraDao;
     //business method
    @SuppressWarnings("unchecked")
    @Override
    public Collection<FlightData> searchFlights(String airlineId,
            String destFrom, String destTo) throws BapiException {
        Object obj = jraDao.searchFlights(airlineId, destFrom, destTo);
        if (obj instanceof Bapiret2[]) {
            throw new BapiException("BAPI returned with an error", (Bapiret2[]) obj);           
        }
        return (Collection<FlightData>)obj;
    }
}

Note the use of Interceptors and Component class-level annotations.

Summary of part 1

What we have achieved so far is to create a session facade with an EJB which uses Spring interceptor to autowire a reference to a JCA connection factory configured on the AS to use a JRA adapter to communicate to the ABAP back-end. We then use a convenient way to execute BAPI calls via Spring's CCI API and the connection factory obtained from JNDI context. Next part will discuss the setup of the JSF 2.0 front-end with the popular Primefaces library.

Some useful links below:

Spring Integration on SAP NetWeaver

6 Comments