Chapter 5. Invoking Session Beans
5.1. About EJB Client Contexts
JBoss EAP introduced the EJB client API for managing remote EJB invocations. The JBoss EJB client API uses the EJBClientContext, which may be associated with and be used by one or more threads concurrently. This means an EJBClientContext can potentially contain any number of EJB receivers. An EJB receiver is a component that knows how to communicate with a server that is capable of handling the EJB invocation. Typically, EJB remote applications can be classified into the following:
- A remote client, which runs as a standalone Java application.
- A remote client, which runs within another JBoss EAP instance.
Depending on the type of remote client, from an EJB client API point of view, there can potentially be more than one EJBClientContext within a JVM.
While standalone applications typically have a single EJBClientContext that may be backed by any number of EJB receivers, this isn’t mandatory. If a standalone application has more than one EJBClientContext, an EJB client context selector is responsible for returning the appropriate context.
In case of remote clients that run within another JBoss EAP instance, each deployed application will have a corresponding EJB client context. Whenever that application invokes another EJB, the corresponding EJB client context is used to find the correct EJB receiver, which then handles the invocation.
5.2. Using Remote EJB Clients
5.2.1. Initial Context Lookup
You can pass the remote server’s address using the PROVIDER_URL
property when creating an initial context:
public class Client { public static void main(String[] args) throws NamingException, PrivilegedActionException, InterruptedException { InitialContext ctx = new InitialContext(getCtxProperties()); String lookupName = "ejb:/server/HelloBean!ejb.HelloBeanRemote"; HelloBeanRemote bean = (HelloBeanRemote)ctx.lookup(lookupName); System.out.println(bean.hello()); ctx.close(); } public static Properties getCtxProperties() { Properties props = new Properties(); props.put(Context.INITIAL_CONTEXT_FACTORY, WildFlyInitialContextFactory.class.getName()); props.put(Context.PROVIDER_URL, "remote+http://127.0.0.1:8080"); props.put(Context.SECURITY_PRINCIPAL, "joe"); props.put(Context.SECURITY_CREDENTIALS, "joeIsAwesome2013!"); return props; } }
The Initial context factory to be used for the lookup is org.wildfly.naming.client.WildFlyInitialContextFactory
.
5.2.2. Remote EJB Configuration File
JBoss EAP features the Elytron security framework. The wildfly-config.xml
file, which is present in the META-INF/
directory of the client application’s class path, allows a wide range of authentication and authorization options for the Elytron security framework and EJB client configuration.
<configuration> <authentication-client xmlns="urn:elytron:1.0.1"> <authentication-rules> <rule use-configuration="default" /> </authentication-rules> <authentication-configurations> <configuration name="default"> <sasl-mechanism-selector selector="DIGEST-MD5" /> <set-user-name name="admin" /> <credentials> <clear-password password="password123!" /> </credentials> </configuration> </authentication-configurations> </authentication-client> <jboss-ejb-client xmlns="urn:jboss:wildfly-client-ejb:3.0"> <connections> <connection uri="remote+http://127.0.0.1:8080" /> </connections> </jboss-ejb-client> </configuration>
As an alternative to embedding the PROVIDER_URL
, SECURITY_PRINCIPAL
and SECURITY_CREDENTIALS
parameters in the initial context, you can use the <connection-uri>
and <authentication-client>
elements in the wildfly-config.xml
file to configure the connection URI and the security settings, respectively.
5.2.3. The ClientTransaction Annotation
The @org.jboss.ejb.client.annotation.ClientTransaction
annotation handles transaction propagation from an EJB client. You can mandate the propagation to fail if the client has no transaction, or prevent the transaction propagation even if the client has one active. You can use the constants of the org.jboss.ejb.client.annotation.ClientTransactionPolicy
interface to control the policy of the ClientTransaction
annotation. The following are the constants of the org.jboss.ejb.client.annotation.ClientTransactionPolicy
interface:
- MANDATORY: Fail with exception when there is no client-side transaction context; propagate the client-side transaction context when it is present.
- NEVER: Invoke without propagating any transaction context; if a client-side transaction context is present, an exception is thrown.
- NOT_SUPPORTED: Invoke without propagating any transaction context whether or not a client-side transaction context is present.
- SUPPORTS: Invoke without a transaction if there is no client-side transaction context; propagate the client-side transaction context if it is present.
If no annotation is present, the default policy is org.jboss.ejb.client.annotation.ClientTransactionPolicy#SUPPORTS
, which means that the transaction is propagated if it is present, but the propagation does not fail, regardless of whether a transaction is present or not.
@ClientTransaction(ClientTransactionPolicy.MANDATORY) @Remote public interface RemoteCalculator { public void callRemoteEjb() { } } @Stateless @Remote(RemoteCalculator.class) public class CalculatorBean implements RemoteCalculator { @Override public void callRemoteEjb() { } }
The annotation allows the remote interface provider to tell the remote interface consumer whether transactions are needed for a method.
5.3. Remote EJB Data Compression
Previous versions of JBoss EAP included a feature where the message stream that contained the EJB protocol message could be compressed. This feature has been included in JBoss EAP 6.3 and later.
Compression currently can only be specified by annotations on the EJB interface which should be on the client and server side. There is not currently an XML equivalent to specify compression hints.
Data compression hints can be specified via the JBoss annotation org.jboss.ejb.client.annotation.CompressionHint
. The hint values specify whether to compress the request, response or request and response. Adding @CompressionHint
defaults to compressResponse=true
and compressRequest=true
.
The annotation can be specified at the interface level to apply to all methods in the EJB’s interface such as:
import org.jboss.ejb.client.annotation.CompressionHint; @CompressionHint(compressResponse = false) public interface ClassLevelRequestCompressionRemoteView { String echo(String msg); }
Or the annotation can be applied to specific methods in the EJB’s interface such as:
import org.jboss.ejb.client.annotation.CompressionHint; public interface CompressableDataRemoteView { @CompressionHint(compressResponse = false, compressionLevel = Deflater.BEST_COMPRESSION) String echoWithRequestCompress(String msg); @CompressionHint(compressRequest = false) String echoWithResponseCompress(String msg); @CompressionHint String echoWithRequestAndResponseCompress(String msg); String echoWithNoCompress(String msg); }
The compressionLevel
setting shown above can have the following values:
- BEST_COMPRESSION
- BEST_SPEED
- DEFAULT_COMPRESSION
- NO_COMPRESSION
The compressionLevel
setting defaults to Deflater.DEFAULT_COMPRESSION
.
Class level annotation with method level overrides:
@CompressionHint public interface MethodOverrideDataCompressionRemoteView { @CompressionHint(compressRequest = false) String echoWithResponseCompress(final String msg); @CompressionHint(compressResponse = false) String echoWithRequestCompress(final String msg); String echoWithNoExplicitDataCompressionHintOnMethod(String msg); }
On the client side ensure the org.jboss.ejb.client.view.annotation.scan.enabled
system property is set to true
. This property tells JBoss EJB Client to scan for annotations.
5.4. EJB Client Remoting Interoperability
The default remote connection port is 8080
. The jboss-ejb-client
properties file looks like this:
remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED=false remote.connections=default remote.connection.default.host=localhost remote.connection.default.port=8080 remote.connection.default.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false
Default Connector
The default connector is http-remoting
.
-
If a client application uses the EJB client library from JBoss EAP 6 and wants to connect to a JBoss EAP 7 server, the server must be configured to expose a remoting connector on a port other than
8080
. The client must then connect using that newly configured connector. A client application that uses the EJB client library from JBoss EAP 7 and wants to connect to a JBoss EAP 6 server must be aware that the server instance does not use the
http-remoting
connector and instead uses aremoting
connector. This is achieved by defining a new client-side connection property.remote.connection.default.protocol=remote
EJB remote calls are supported for JBoss EAP 7 with JBoss EAP 6 only.
Besides EJB client remoting interoperability, you can connect to legacy clients using the following options:
- Configure the ORB for JTS Transactions in the JBoss EAP Configuration Guide.
5.5. Configure IIOP for Remote EJB Calls
JBoss EAP supports CORBA/IIOP-based access to EJBs deployed on JBoss EAP.
The <iiop>
element is used to enable IIOP, CORBA, invocation of EJBs. The presence of this element means that the iiop-openjdk
subsystem is installed. The <iiop>
element includes the following two attributes:
-
enable-by-default
: If this istrue
, then all the EJBs with EJB 2.x home interfaces are exposed through IIOP. Otherwise they must be explicitly enabled throughjboss-ejb3.xml
. -
use-qualified-name
: If this istrue
, then the EJBs are bound to the CORBA naming context with a binding name that contains the application and modules name of the deployment, such asmyear/myejbjar/MyBean
. If this isfalse
, then the default binding name is simply the bean name.
IIOP calls can be done only with EJB 2 beans. EJB 3 beans are not supported by IIOP in JBoss EAP 7.1.
Enabling IIOP
To enable IIOP you must have the IIOP OpenJDK ORB subsystem installed, and the <iiop/>
element present in the ejb3
subsystem configuration. The standalone-full.xml
configuration that comes with the distribution has both of these enabled.
IIOP is configured in the iiop-openjdk
subsystem of the server configuration file.
<subsystem xmlns="urn:jboss:domain:iiop-openjdk:1.0">
Use the following management CLI command to access and update the iiop-openjdk
subsystem.
/subsystem=iiop-openjdk
The IIOP element takes two attributes that control the default behavior of the server.
<subsystem xmlns="urn:jboss:domain:ejb3:1.2"> ... <iiop enable-by-default="false" use-qualified-name="false"/> ... </subsystem>
The following management CLI command adds the <iiop>
element under the ejb3
subsystem:
/subsystem=ejb3/service=iiop:add(enable-by-default=false, use-qualified-name=false)
Create an EJB That Communicates Using IIOP
The following example demonstrates how to make a remote IIOP call from the client:
Create an EJB 2 bean on the server:
@Remote(IIOPRemote.class) @RemoteHome(IIOPBeanHome.class) @Stateless public class IIOPBean { public String sayHello() throws RemoteException { return "hello"; } }
Create a home implementation, which has a mandatory method
create()
. This method is called by the client to obtain proxy of remote interface to invoke business methods:public interface IIOPBeanHome extends EJBHome { public IIOPRemote create() throws RemoteException; }
Create a remote interface for remote connection to the EJB:
public interface IIOPRemote extends EJBObject { String sayHello() throws RemoteException; }
Introduce the bean for remote call by creating a descriptor file
jboss-ejb3.xml
inMETA-INF
:<?xml version="1.0" encoding="UTF-8"?> <jboss:ejb-jar xmlns:jboss="http://www.jboss.com/xml/ns/javaee" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:iiop="urn:iiop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.jboss.com/xml/ns/javaee http://www.jboss.org/j2ee/schema/jboss-ejb3-2_0.xsd http://java.sun.com/xml/ns/javaee http://www.jboss.org/j2ee/schema/jboss-ejb3-spec-2_0.xsd urn:iiop jboss-ejb-iiop_1_0.xsd" version="3.1" impl-version="2.0"> <assembly-descriptor> <iiop:iiop> <ejb-name>*</ejb-name> </iiop:iiop> </assembly-descriptor> </jboss:ejb-jar>
NoteThe packed beans along with the descriptor in the JAR file is now ready to be deployed to the JBoss EAP container.
Create a context at the client side:
System.setProperty("com.sun.CORBA.ORBUseDynamicStub", "true"); final Properties props = new Properties(); props.put(Context.PROVIDER_URL, "corbaloc::localhost:3528/JBoss/Naming/root"); props.setProperty(Context.URL_PKG_PREFIXES, "org.jboss.iiop.naming:org.jboss.naming.client"); props.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.cosnaming.CNCtxFactory"); props.put(Context.OBJECT_FACTORIES, "org.jboss.tm.iiop.client.IIOPClientUserTransactionObjectFactory");
NoteThe client will need to have the
wildfly iiop openjdk
library added to its class path. The client might also need to add theorg.wildfly:wildfly-iiop-openjdk
artifact as Maven dependency.Use the context lookup to narrow the reference to the
IIOPBeanHome
home interface. Then call the home interfacecreate()
method to access the remote interface, which allows you to call its methods:try { Context context = new InitialContext(props); final Object iiopObj = context.lookup(IIOPBean.class.getSimpleName()); final IIOPBeanHome beanHome = (IIOPBeanHome) PortableRemoteObject.narrow(iiopObj, IIOPBeanHome.class); final IIOPRemote bean = beanHome.create(); System.out.println("Bean saying: " + bean.sayHello()); } catch (Exception e) { e.printStackTrace(); }
5.6. Configure the EJB Client Address
You can determine the EJB client address using the SessionContext
interface, as shown in the example below.
public class HelloBean implements HelloBeanRemote { @Resource SessionContext ctx; private Long counter; public HelloBean() { } @PostConstruct public void init() { counter = 0L; } @Override @RolesAllowed("users") public String hello() { final String message = "method hello() invoked by user " + ctx.getCallerPrincipal().getName() + ", source addr = " + ctx.getContextData().get("jboss.source-address").toString(); System.out.println(message); return message; } }
Standalone Client Configuration
You can configure the outbound-bind-addresses
element within the worker
element having namespace urn:xnio:3.5
in the wildfly-client.xml
file. The bind-address
sub-element takes the attributes match
, bind-address
, bind-port
, as defined below.
The following is an example of the standalone client configuration using the wildfly-client.xml
file.
<configuration> <worker xmlns="urn:xnio:3.5"> <worker-name value="default"/> <outbound-bind-addresses> <bind-address bind-address=IP_ADDRESS_TO_BIND_TO bind-port=OPTIONAL_SOURCE_PORT_NUMBER match=CIDR_BLOCK /> </outbound-bind-addresses> </worker> </configuration>
The outbound-bind-address
requires the following attributes:
-
match
is a Classless Inter-Domain Routing (CIDR) block, such as10.0.0.0/8
,ff00::\8
,0.0.0.0/0
,::/0
. -
bind-address
specifies the IP address to bind to when the destination address matches the CIDR block specified in thematch
parameter. It should be the same address family as the CIDR block. bind-port
is an optional source port number that defaults to0
.If no matching expression exists, then the outbound socket is not explicitly bound.
Container-based Configuration
Container-based configuration of the EJB client address is similar to the standalone client configuration defined in the wildfly-client.xml
file.
The example below configures the outbound-bind-address
on the default worker
element of the io
subsystem, which the ejb3
subsystem uses by default.
/subsystem=io/worker=default/outbound-bind-address=SPECIFY_OUTBOUND_BIND_ADDRESS:add(bind-address=IP_ADDRESS_TO_BIND_TO, bind-port=OPTIONAL_SOURCE_PORT_NUMBER, match=CIDR_BLOCK)
5.7. EJB Invocation Over HTTP
EJB invocation over HTTP is provided as Technology Preview only. Technology Preview features are not supported with Red Hat production service level agreements (SLAs), might not be functionally complete, and Red Hat does not recommend to use them for production. These features provide early access to upcoming product features, enabling customers to test functionality and provide feedback during the development process.
See Technology Preview Features Support Scope on the Red Hat Customer Portal for information about the support scope for Technology Preview features.
EJB invocation over HTTP includes two distinct parts: the client-side and the server-side implementations.
5.7.1. Client-side Implementation
The client-side implementation consists of an EJBReceiver
that uses the Undertow HTTP client to invoke the server. Connection management is handled automatically using a connection pool.
In order to configure an EJB client application to use HTTP transport, you must add the following dependency on the HTTP transport implementation:
<dependency> <groupId>org.wildfly.wildfly-http-client</groupId> <artifactId>wildfly-http-ejb-client</artifactId> </dependency>
To perform the HTTP invocation, you must use the http
URL scheme and include the context name of the HTTP invoker, wildfly-services
. For example, if you are using remote+http://localhost:8080
as the target URL, in order to use the HTTP transport, you must update this to http://localhost:8080/wildfly-services
.
5.7.2. Server-side Implementation
The server-side implementation consists of a service that handles the incoming HTTP requests, unmarshals them and passes the result to the internal EJB invocation code.
In order to configure the server, the http-invoker
must be enabled on each of the virtual hosts that you wish to use in the undertow
subsystem. This is enabled by default in the standard configurations. If it is disabled, it can be re-enabled using the following management CLI command:
/subsystem=undertow/server=default-server/host=default-host/setting=http-invoker:add(http-authentication-factory=myfactory, path='wildfly-services')
http-invoker
has two attributes: a path
that defaults to wildfly-services
, and one of the following:
-
An
http-authentication-factory
that must be a reference to an Elytronhttp-authentication-factory
, as shown in the above command. -
A legacy
security-realm
.
Note that the above two attributes are mutually exclusive: you cannot specify both an http-authentication-factory
and a security-realm
at the same time.
Any deployment that aims to use the http-authentication-factory
must use Elytron security with the same security domain corresponding to the specified HTTP authentication factory.