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;
    }
}
Note

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:client:1.2">
      <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.

Note

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 a remoting connector. This is achieved by defining a new client-side connection property.

    remote.connection.default.protocol=remote
Note

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:

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 is true, then all the EJBs with EJB 2.x home interfaces are exposed through IIOP. Otherwise they must be explicitly enabled through jboss-ejb3.xml.
  • use-qualified-name: If this is true, 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 as myear/myejbjar/MyBean. If this is false, then the default binding name is simply the bean name.
Note

Even though a RemoteHome interface is not normally required for EJB 3 remote calls, it is required for any EJB 3 bean that is exposed using IIOP. You must then enable IIOP using the jboss-ejb3.xml file, or by enabling IIOP for all EJBs in the standalone-full.xml configuration file.

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:2.1">

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:5.0">
  ...
  <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:

  1. 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";
        }
    }
  2. 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;
    }
  3. Create a remote interface for remote connection to the EJB:

    public interface IIOPRemote extends EJBObject {
        String sayHello() throws RemoteException;
    }
  4. Introduce the bean for remote call by creating a descriptor file jboss-ejb3.xml in META-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>
    Note

    The packed beans along with the descriptor in the JAR file is now ready to be deployed to the JBoss EAP container.

  5. 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");
    Note

    The client will need to have the wildfly iiop openjdk library added to its class path. The client might also need to add the org.wildfly:wildfly-iiop-openjdk artifact as Maven dependency.

  6. Use the context lookup to narrow the reference to the IIOPBeanHome home interface. Then call the home interface create() 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 as 10.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 the match parameter. It should be the same address family as the CIDR block.
  • bind-port is an optional source port number that defaults to 0.

    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 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 Elytron http-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.

Note

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.