graniteds.orgCommunity Documentation

Chapter 7. Integration with EJB3

7.1. Using the RemoteService API
7.1.1. Basic Remoting Example
7.1.2. Common configuration
7.1.3. Configuration for Remote EJBs
7.1.4. Automatic Configuration of EJB Destinations
7.1.5. Configuration for Stateful EJBs
7.1.6. Security
7.2. Using the Tide API
7.2.1. Configuration
7.2.2. Basic remoting with dependency injection
7.2.3. Typesafe remoting with dependency injection

EJB 3 are an important part of the Java EE 5 platform. They provide a powerful framework for managing and securing enterprise services in an application server (session beans) as well as an powerful persistence and query language system (JPA).

GraniteDS provides access to EJB 3 services via either the RemoteService API or the Tide API for Session Beans methods calls, and fully supports serialization of JPA entities from and to your Java client application, taking care of lazily loaded associations; both collections and proxies. This support for JPA entity beans is covered in the section JPA and lazy initialization, so this section will only describe how to call remotely stateless and stateful session beans from a Java client application. GraniteDS also integrates with container security for authentication and role-based authorization.

The client-side usage of the RemoteService API is completely independent of the server technology, so everything described in the Remoting chapter applies for EJBs. This section will only describe the particular configuration required in various use cases of EJB services.

Configuring remoting for EJB 3 services simply requires adding the org.granite.messaging.service.EjbServiceFactory service factory in services-config.xml and specifying its JNDI lookup string property.

All remoting examples from the Remoting chapter apply for EJBs, here is a basic example:



public interface HelloService {
    public String hello(String name);
}
@Stateless
@Local(HelloService.class)
@RemoteDestination(id="helloService")
public class HelloServiceBean implement HelloService {
    public String hello(String name) {
        return "Hello " + name;
    }
}
            


AMFRemotingChannel channel = new AMFRemotingChannel(transport, "graniteamf", 
    new URI("http://localhost:8080/helloworld/graniteamf/amf.txt"));
RemoteService srv = new RemoteService(channel, "hello");
        
srv.newInvocation("hello", "Barack").setTimeToLive(5, TimeUnit.SECONDS)
    .addListener(new ResultFaultIssuesResponseListener() {
    
    @Override
    public void onResult(ResultEvent event) {
        System.out.println("Result: " + event.getResult());
    }
    @Override
    public void onFault(FaultEvent event) {
        System.err.println("Fault: " + event.toString());
    }
    @Override
    public void onIssue(IssueEvent event) {
        System.err.println("Issue: " + event.toString());
    }
}).invoke();
            

The main part of the configuration is the factory declaration in the file services-config.xml :



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

<services-config>

    <services>
        <service
            id="granite-service"
            class="flex.messaging.services.RemotingService"
            messageTypes="flex.messaging.messages.RemotingMessage">
            <destination id="personService">
                <channels>
                    <channel ref="my-graniteamf"/>
                </channels>
                <properties>
                    <factory>ejbFactory</factory>
                </properties>
            </destination>
        </service>
    </services>

    <factories>
        <factory id="ejbFactory" class="org.granite.messaging.service.EjbServiceFactory">
            <properties>
                <lookup>myapp.ear/{capitalized.destination.id}Bean/local</lookup>
            </properties>
        </factory>
    </factories>

    <channels>
        <channel-definition id="my-graniteamf" class="mx.messaging.channels.AMFChannel">
            <endpoint
                uri="http://{server.name}:{server.port}/{context.root}/graniteamf/amf"
                class="flex.messaging.endpoints.AMFEndpoint"/>
        </channel-definition>
    </channels>

</services-config>
            

Two elements are important in this configuration :

The JNDI lookup string is common for all EJB 3 destinations, and thus contains placeholders that will be replaced at runtime depending on the destination that is called. {capitalized.destination.id} will be replaced by the destination id with the first letter in capital, for example personService will become myApp/PersonServiceBean/local. {destination.id} can alternatively be used. Note that some Java EE servers do not expose EJB local interfaces in the global JNDI context, so you will have to use a local JNDI reference and add an ejb-local-ref section in web.xml for each EJB exposed to JNDI.



<ejb-local-ref>
    <ejb-ref-name>myapp.ear/PeopleServiceBean</ejb-ref-name>
    <ejb-ref-type>Session</ejb-ref-type>
    <local-home/>
    <local>com.myapp.service.PeopleService</local>
</ejb-local-ref>
            


<factory id="ejbFactory" class="org.granite.messaging.service.EjbServiceFactory">
    <properties>
        <lookup>java:comp/env/myapp.ear/{capitalized.destination.id}Bean</lookup>
    </properties>
</factory>

            

Of course you can share the same factory with many EJB destinations.



<destination id="person">
    <channels>
        <channel ref="my-graniteamf"/>
    </channels>
    <properties>
        <factory>ejbFactory</factory>
    </properties>
</destination>

<destination id="product">
    <channels>
        <channel ref="my-graniteamf"/>
    </channels>
    <properties>
        <factory>ejbFactory</factory>
    </properties>
</destination>
            

By default GraniteDS will lookup the bean in JNDI with the default InitialContext. To access remote EJB services you have to specify the JNDI context environment that will be used for remote lookup in the factory definition of services-config.xml.

The parameters generally depend on the remote application server. Please refer to the standard JNDI Context API documention and to the documentation of your application server for more details.



...
<factory id="ejbFactory" class="org.granite.messaging.service.EjbServiceFactory">
    <properties>
        <lookup>myApp/{capitalized.destination.id}Bean/local</lookup>

        <!-- InitialContext parameters -->
        <initial-context-environment>
            <property>
                <name>Context.PROVIDER_URL</name>
                <value>...</value>
            </property>
            <property>
                <name>Context.INITIAL_CONTEXT_FACTORY</name>
                <value>...</value>
            </property>
            <property>
                <name>Context.URL_PKG_PREFIXES</name>
                <value>...</value>
            </property>
            <property>
                <name>Context.SECURITY_PRINCIPAL</name>
                <value>...</value>
            </property>
            <property>
                <name>Context.SECURITY_CREDENTIALS</name>
                <value>...</value>
            </property>
        </initial-context-environment>
    </properties>
</factory>
...
            

For JBoss Application Server for example this declaration looks like this:



...
<factory id="ejbFactory" class="org.granite.messaging.service.EjbServiceFactory">
    <properties>
        <lookup>myApp/{capitalized.destination.id}Bean/local</lookup>

        <!-- InitialContext parameters -->
        <initial-context-environment>
            <property>
                <name>Context.PROVIDER_URL</name>
                <value>jnp://remotehostname:1099</value>
            </property>
            <property>
                <name>Context.INITIAL_CONTEXT_FACTORY</name>
                <value>org.jnp.interfaces.NamingContextFactory</value>
            </property>
            <property>
                <name>Context.URL_PKG_PREFIXES</name>
                <value>org.jboss.naming:org.jnp.interfaces</value>
            </property>
        </initial-context-environment>
    </properties>
</factory>
...
            

This is annoying to have to declare each and every EJB exposed to Flex remoting in services-config.xml. To avoid this step, it is possible to instruct GraniteDS to search EJB services in the application classpath.

Note however that this cannot work with remote EJBs as GraniteDS will obviously not have access to the remote classpath.

To enable automatic destination discovery, you simply have to enable the scan property in granite-config.xml:



<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE granite-config PUBLIC
    "-//Granite Data Services//DTD granite-config internal//EN"
    "http://www.graniteds.org/public/dtd/3.0.0/granite-config.dtd">

<granite-config scan="true">
   ...
</granite-config>
            

Then you have to add a simple marker file (even empty) META-INF/services-config.properties in every EJB jar (or in WEB-INF/classes if you use EJB 3.1 packaged in a war). Then GraniteDS will scan these jars at startup and look for EJB classes annotated with @RemoteDestination. The annotation can be put either on the EJB interface or on the EJB implementation, but it's recommended to put it on the EJB interface.



@Stateless
@Local(PersonService.class)
@RemoteDestination(id="person", securityRoles={"user","admin"})
public class PersonServiceBean implements PersonService {
    ...
}
            

The @RemoteDestination annotation additionally supports the following attributes:

As shown below, the service, factory and channel sections are still required in your services-config.xml file, but the service part will not contain any destination. So, with any number of EJBs annotated this way, the services-config.xml file may be defined as follows:



<?xml version="1.0" encoding="UTF-8"?>
<services-config>
    <services>
        <service
            id="granite-service"
            class="flex.messaging.services.RemotingService"
            messageTypes="flex.messaging.messages.RemotingMessage">
            <!-- no need to declare destinations here -->
        </service>
    </services>
    <factories>
        <factory id="ejbFactory" class="org.granite.messaging.service.EjbServiceFactory">
            <properties>
                <lookup>myApp/{capitalized.destination.id}Bean/local</lookup>
            </properties>
        </factory>
    </factories>
    <channels>
        <channel-definition id="my-graniteamf" class="mx.messaging.channels.AMFChannel">
            <endpoint
                uri="http://{server.name}:{server.port}/{context.root}/graniteamf/amf"
                class="flex.messaging.endpoints.AMFEndpoint"/>
        </channel-definition>
    </channels>
</services-config>
            

As the destinations are not defined in services-config.xml any more, you will have to setup the RemoteObject endpoint manually in ActionScript (see here for details).

Most of what has been described for stateless beans also applies for stateful beans, however stateful beans have a different lifecycle.

GraniteDS stores the reference of stateful EJBs retrieved from JNDI in the HTTP session so it can keep the correct instance between remote calls. Take care that the timeout for HTTP session expiration should be consistent with the timeout for EJB3 stateful beans expiration.

GraniteDS has to know a bit more information about stateful beans than for stateless beans, here is an example of services-config.xml for the following EJB:



package com.myapp.services;
import javax.ejb.Local;
import javax.ejb.Remove;
import javax.ejb.Stateful;
@Stateful
@Local(PositionService.class)
public class PositionServiceBean implements PositionService {
    int x = 300;
    
    public int getX() {
        return x;
    }
    public void saveX(int x) {
        this.= x;
    }
    @Remove
    public void remove() {
    }
}
            


<destination id="position">
    <channels>
        <channel ref="my-graniteamf"/>
    </channels>
    <properties>
        <factory>ejbFactory</factory>

        <!-- Specific for stateful beans -->
        <ejb-stateful>
            <remove-method>
            <signature>remove</signature>
            <retain-if-exception>false</retain-if-exception>
            </remove-method>
        </ejb-stateful>
    </properties>
</destination>
            

The configuration of the destination is similar to the one used for stateless beans, except for the additional ejb-stateful subsection. The presence of this ejb-stateful node, even empty, informs GDS that this EJB 3 is stateful and should be managed as such. Otherwise, the bean will be considered stateless and only one instance will be shared between all users.

The inner remove-method node contains information about the remove() methods of your stateful bean:

You may of course add multiple remove-method nodes in the same ejb-stateful node if necessary.

When using automatic configuration with classpath scanning, stateful EJBs are automatically detected with the @Stateful annotation and properly configured.

You can easily protect access to your EJB destinations with destination-based security. Please refer to the security chapter.

GraniteDS will then pass the user credentials from the client RemotingChannel to the EJB security context, making possible to use role-based authorization with the EJB destination.

Here is an example configuration in services-config.xml:



<destination id="personService">
    <channels>
        <channel ref="my-graniteamf"/>
    </channels>
    <properties>
        <factory>ejbFactory</factory>
    </properties>
    <security>
        <security-constraint>
            <auth-method>Custom</auth-method>
            <roles>
                <role>user</role>
                <role>admin</role>
            </roles>
        </security-constraint>
    </security>
</destination>
            


@Stateless
@Local(PersonService.class)
public class PersonServiceBean implements PersonService {
    
    @PersistenceContext
    protected EntityManager manager;
    public List<Person> findAllPersons() {
        return manager.createQuery("select distinct p from Person p").getResultList();
    }
    @RolesAllowed({"admin"})
    public Person createPerson(Person person) {
        return manager.merge(person);
    }
    @RolesAllowed({"admin"})
    public Person modifyPerson(Person person) {
        return manager.merge(person);
    }
    @RolesAllowed({"admin"})
    public void deletePerson(Person person) {
        person = manager.find(Person.class, person.getId());
        manager.remove(person);
    }
}
            

With this configuration, only authenticated users having either the user or admin roles will be able to call the EJB remotely from the client. Then the EJB container will enforce the particular access on each method due to the @RolesAllowed annotation and may throw a EJBAccessException.

Most of what is described in the Tide Remoting section applies for EJB 3, however GraniteDS also provides an improved integration with EJB 3 services.

There are a few noticeable differences in the configuration in this case.

Here is a default configuration suitable for most cases:



<granite-config scan="true">
    ...
    
    <tide-components>
        <tide-component annotated-with="org.granite.messaging.service.annotations.RemoteDestination"/>
    </tide-components>
    
</granite-config>   
            


<services-config>

    <services>
        <service id="granite-service"
            class="flex.messaging.services.RemotingService"
            messageTypes="flex.messaging.messages.RemotingMessage">
            <!--
             ! Use "tideEjbFactory" and "my-graniteamf" for "ejb" destination (see below).
             ! The destination must be "ejb" when using Tide with default configuration.
             !-->
            <destination id="ejb">
                <channels>
                    <channel ref="my-graniteamf"/>
                </channels>
                <properties>
                    <factory>tideEjbFactory</factory>
                    <entity-manager-factory-jndi-name>java:/DefaultEMF</entity-manager-factory-jndi-name>
                </properties>
            </destination>
        </service>
    </services>

    <!--
     ! Declare tideEjbFactory service factory.
     !-->
    <factories>
        <factory id="tideEjbFactory" class="org.granite.tide.ejb.EjbServiceFactory">
            <properties>
                <lookup>myapp.ear/{capitalized.component.name}Bean/local</lookup>
            </properties>
        </factory>
    </factories>

    <!--
     ! Declare my-graniteamf channel.
     !-->
    <channels>
        <channel-definition id="my-graniteamf" class="mx.messaging.channels.AMFChannel">
            <endpoint
                uri="http://{server.name}:{server.port}/{context.root}/graniteamf/amf"
                class="flex.messaging.endpoints.AMFEndpoint"/>
        </channel-definition>
    </channels>

</services-config>    
            

The destination named ejb will be the one and only destination required for all EJB destinations.

The property lookup of the factory defines the lookup string used by Tide to lookup the EJBs in JNDI. The example above is suitable for JBoss, please refer to your application server documentation for other servers. Placeholders can be defined in this lookup string that will be replaced at runtime for each EJB: {capitalized.component.name} is the name used on the client.



<ejb-local-ref>
    <ejb-ref-name>myapp/PeopleServiceBean</ejb-ref-name>
    <ejb-ref-type>Session</ejb-ref-type>
    <local-home/>
    <local>com.myapp.service.PeopleService</local>
</ejb-local-ref>
            


<factory id="tideEjbFactory" class="org.granite.tide.ejb.EjbServiceFactory">
    <properties>
        <lookup>java:comp/env/myapp/{capitalized.component.name}Bean</lookup>
    </properties>
</factory>

            

The property entity-manager-factory-name is necessary only when using transparent remote lazy loading of collections. It should be the JNDI name that GraniteDS can use to lookup the EntityManagerFactory in JNDI. Alternatively you can instead specify entity-manager-name, then GraniteDS will lookup for an EntityManager. JBoss server can expose these two elements in the global JNDI by adding these lines in persistence.xml:



<persistence-unit name="ejb-pu">
    ...
    <properties>
        ...
        <property name="jboss.entity.manager.factory.jndi.name" value="java:/DefaultEMF"/>
        <property name="jboss.entity.manager.jndi.name" value="java:/DefaultEM"/>
    </properties>
</persistence-unit>
            

For other application servers that does not expose the persistence unit in JNDI, you will have to use a local name and add persistence-unit-ref in web.xml.



<persistence-unit-ref>
    <persistence-unit-ref-name>ejb-pu</persistence-unit-ref-name>
</persistence-unit-ref>

            


<destination id="ejb">
    <channels>
        <channel ref="graniteamf"/>
    </channels>
    <properties>
        <factory>tideEjbFactory</factory>
        <entity-manager-factory-jndi-name>java:comp/env/ejb-pu</entity-manager-factory-jndi-name>
    </properties>
</destination>
            

When using EJB3, the only difference on the client is that you have to use the destination named ejb to build the ServerSession. Here is a simple example of remoting with an Spring-injected client proxy for an EJB service:



public class HelloController {
    @Inject @Qualifier("helloService")
    private Component helloService;
    
    public void hello(String to) {
        // Asynchronous call using handlers
        helloService.call("hello", to, new TideResponder<String>() {
            @Override
            public void result(TideResultEvent<String> result) {
                System.out.println("Async result: " + result.getResult());
            }
            
            @Override
            public void fault(TideFaultEvent fault) {
                System.err.println("Fault: " + fault.getFault());
            }
        };
    }
    
    public String helloSync(String to) {    
        // Synchronous wait of Future result
        Future<String> futureResult = helloService.call("hello", to);
        String result = futureResult.get();
        System.out.println("Sync result: " + result);
        return result;
    }
}
            

If you have generated typed client proxies, it can be further simplified to something like this:



public class HelloController {
    @Inject
    private HelloService helloService;
    
    public void hello(String to) {
        // Asynchronous call using handlers
        helloService.hello(to, new TideResponder<String>() {
            @Override
            public void result(TideResultEvent<String> result) {
                System.out.println("Async result: " + result.getResult());
            }
            
            @Override
            public void fault(TideFaultEvent fault) {
                System.err.println("Fault: " + fault.getFault());
            }
        };
    }
    
    public String helloSync(String to) {    
        // Synchronous wait of Future result
        Future<String> futureResult = helloService.hello(to);
        String result = futureResult.get();
        System.out.println("Sync result: " + result);
        return result;
    }
}
            

This is almost identical to the standard Tide API described in the Tide remoting section, and all other methods apply for EJB.

You can benefit from the capability of the Gfx code generator (see here) to generate a strongly typed Java client proxy from the EJB3 interface when it is annotated with @RemoteDestination. In this case, you can inject a typesafe reference to your service and get better compile time error checking and auto completion in your IDE:



public class HelloController {
    @Inject @Qualifier("helloService")
    private HelloService helloService;
    
    // Asynchronous call using handlers
    helloService.hello("Barack", new TideResponder<String>() {
        @Override
        public void result(TideResultEvent<String> result) {
            System.out.println("Async result: " + result.getResult());
        }
        
        @Override
        public void fault(TideFaultEvent fault) {
            System.err.println("Fault: " + fault.getFault());
        }
    };
    
    // Synchronous wait of Future result
    Future<String> futureResult = helloService.hello("Barack");
    String result = futureResult.get();
    System.out.println("Sync result: " + result);
}
            

Note that as there is only one instance of HelloService, you may also omit the Qualifier annotation and use typesafe injection with @Inject only.