graniteds.orgCommunity Documentation
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 EJB service factory declaration and the reference to in in our destination
The JNDI lookup string defined in the lookup property of the factory
In Java EE 6 compliant application servers such as JBoss 6 and GlassFish 3, you can use the standard global naming specification :
java:global/{context.root}/{capitalized.destination.id}Bean.
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:
id is mandatory and is the destination name
service is optional if there is only one service for RemotingMessage defined in services-config.xml.
Otherwise this should be the name of the service.
channel is optional if there is only one channel defined in services-config.xml.
Otherwise this should be the id of the target channel.
channels may be used instead of channel to define a failover channel.
factory is optional if there is only one factory in services-config.xml. Otherwise this should be the factory id.
securityRoles is an array of role names for securing the destination.
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 = 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:
signature: This is the name of the method, optionally followed by a parameter list.
If your remove() method has arguments, the signature should follow the conventions used in java.lang.reflect.Method.toString().
For example, with the following remove() method:
@Remove
public int remove(boolean arg1, Integer arg2, String[] arg3) {...}
... you should write this signature:
<signature>remove(boolean,java.lang.Integer,java.lang.String[])</signature>
retain-if-exception (optional): This is the equivalent of the @Remove annotation attribute;
the default is false.
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.
It is mandatory to use automatic classpath scanning as Tide needs to have access to the actual implementation of the EJB and not only to its interface. Consequently this is currently not possible to use remote EJBs as Tide-enabled destinations.
You can define in the tide-annotations section of granite-config.xml the conditions
used to enable remote access to EJB destinations (for example all EJBs annotated with a particular annotation).
You have to configure the specific Tide/EJB3 org.granite.tide.ejb.EjbServiceFactory service factory in services-config.xml.
You have to configure a unique Tide/EJB3 destination named ejb in services-config.xml
You have to retrieve the Tide context in Flex with Ejb.getInstance().getEjbContext()
instead of Tide.getInstance().getContext().
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.
In Java EE 6 compliant application servers such as JBoss 6 and GlassFish 3, you can use the standard global naming specification :
java:global/{context.root}/{capitalized.component.name}Bean.
In many JEE servers (GlassFish v2 for example, but not JBoss), EJB local interfaces are not published in the global JNDI.
To be able to call them through Tide, you will have to specify ejb-local-ref definitions for each EJB in web.xml
and use a java:comp/env local JNDI name.
<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.