Introduction
The aim of this wiki is to explain what set up is required for EJB2 beans, EJB3 beans, JMS (JBoss Messaging) and JNDI to be accessed either via HTTP or HTTPS using JBoss Remoting based invokers within the scope of AS/EAP 4.2.x+. Bear in mind that from the AS/EAP versions specified, Remoting based invokers replace legacy JRMP, Pooled and HTTP invokers.
The set up will include:
AS/EAP server side configuration.
EJB2/3 deployment requirements.
EJB2/3 client code.
- JMS client code.
Note: To use JMS (over JBoss Messaging) with Servlet invoker in Remoting, Remoting 2.2.2.SP12. This remoting version will be included in EAP 4.2.0.GA_CP07 and 4.3.0.GA_CP05.
Server Side Setup
Step 1. Grab a copy of JBoss AS 4.2.x+ or JBoss EAP 4.2.x/4.3.x+ and create a new server configuration (based on /default).
Step 2. Go to deploy/http-invoker.sar/invoker.war/WEB-INF/classes/ directory and ZIP up the classes into a JAR called jboss-http-naming.jar. Put this jar in the server's lib directory so that both the legacy and unified http invoker share these classes.
Step 3. Follow instructions in SSLSetup to generate the necessary SSL certificates depending on your security requirements and enable HTTPS connector.
Step 4. Take the attached http-uinvoker-v2.sar.zip file and unzip it in the deploy/ directory:
EJB2 Deployments Setup
Step 5. Take an SLSB and make it use the unified invoker with HTTP and HTTPS transport:
<?xml version="1.0" encoding="UTF-8"?> <ejb-jar> <enterprise-beans> <session> <ejb-name>TimeTellerEjbHttp</ejb-name> <home>com.acme.ejb2.slsb.TimeTellerHome</home> <remote>com.acme.ejb2.slsb.TimeTeller</remote> <ejb-class>com.acme.ejb2.slsb.TimeTellerBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> </session> <session> <ejb-name>TimeTellerEjbHttps</ejb-name> <home>com.acme.ejb2.slsb.TimeTellerHome</home> <remote>com.acme.ejb2.slsb.TimeTeller</remote> <ejb-class>com.acme.ejb2.slsb.TimeTellerBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> </session> </enterprise-beans> </ejb-jar>
In the jboss.xml, make sure each bean is going to use the corresponding Unified Invoker created above, configuring the file to look something like this:
<?xml version="1.0"?> <jboss> <enterprise-beans> <session> <ejb-name>TimeTellerEjbHttp</ejb-name> <jndi-name>ejb/TimeTellerEjbHttp</jndi-name> <configuration-name>Unified Http Stateless SessionBean</configuration-name> </session> <session> <ejb-name>TimeTellerEjbHttps</ejb-name> <jndi-name>ejb/TimeTellerEjbHttps</jndi-name> <configuration-name>Unified Https Stateless SessionBean</configuration-name> </session> </enterprise-beans> <invoker-proxy-bindings> <invoker-proxy-binding> <name>stateless-unified-http-invoker</name> <invoker-mbean>jboss:service=invoker,type=unified,transport=servlet</invoker-mbean> <proxy-factory>org.jboss.proxy.ejb.ProxyFactory</proxy-factory> <proxy-factory-config> <client-interceptors> <home> <interceptor>org.jboss.proxy.ejb.HomeInterceptor</interceptor> <interceptor>org.jboss.proxy.SecurityInterceptor</interceptor> <interceptor>org.jboss.proxy.TransactionInterceptor</interceptor> <interceptor call-by-value="false">org.jboss.invocation.InvokerInterceptor</interceptor> <interceptor call-by-value="true">org.jboss.invocation.MarshallingInvokerInterceptor</interceptor> </home> <bean> <interceptor>org.jboss.proxy.ejb.StatelessSessionInterceptor</interceptor> <interceptor>org.jboss.proxy.SecurityInterceptor</interceptor> <interceptor>org.jboss.proxy.TransactionInterceptor</interceptor> <interceptor call-by-value="false">org.jboss.invocation.InvokerInterceptor</interceptor> <interceptor call-by-value="true">org.jboss.invocation.MarshallingInvokerInterceptor</interceptor> </bean> </client-interceptors> </proxy-factory-config> </invoker-proxy-binding> <invoker-proxy-binding> <name>stateless-unified-https-invoker</name> <invoker-mbean>jboss:service=invoker,type=unified,transport=sslservlet</invoker-mbean> <proxy-factory>org.jboss.proxy.ejb.ProxyFactory</proxy-factory> <proxy-factory-config> <client-interceptors> <home> <interceptor>org.jboss.proxy.ejb.HomeInterceptor</interceptor> <interceptor>org.jboss.proxy.SecurityInterceptor</interceptor> <interceptor>org.jboss.proxy.TransactionInterceptor</interceptor> <interceptor call-by-value="false">org.jboss.invocation.InvokerInterceptor</interceptor> <interceptor call-by-value="true">org.jboss.invocation.MarshallingInvokerInterceptor</interceptor> </home> <bean> <interceptor>org.jboss.proxy.ejb.StatelessSessionInterceptor</interceptor> <interceptor>org.jboss.proxy.SecurityInterceptor</interceptor> <interceptor>org.jboss.proxy.TransactionInterceptor</interceptor> <interceptor call-by-value="false">org.jboss.invocation.InvokerInterceptor</interceptor> <interceptor call-by-value="true">org.jboss.invocation.MarshallingInvokerInterceptor</interceptor> </bean> </client-interceptors> </proxy-factory-config> </invoker-proxy-binding> </invoker-proxy-bindings> <container-configurations> <container-configuration extends="Standard Stateless SessionBean"> <container-name>Unified Http Stateless SessionBean</container-name> <invoker-proxy-binding-name>stateless-unified-http-invoker</invoker-proxy-binding-name> </container-configuration> <container-configuration extends="Standard Stateless SessionBean"> <container-name>Unified Https Stateless SessionBean</container-name> <invoker-proxy-binding-name>stateless-unified-https-invoker</invoker-proxy-binding-name> </container-configuration> </container-configurations> </jboss>
EJB2 Client Code
Step 6. For HTTP: Retrieve the JNDI proxy via the HTTP, do the lookup and call your EJB which will communicate with the server via HTTP as well.
Properties p = new Properties(); p.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.HttpNamingContextFactory"); p.put(Context.PROVIDER_URL, "http://localhost:8080/unified-invoker/JNDIFactory/?return-exception=true"); p.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces"); ctx = new InitialContext(p); TimeTellerHome home = (TimeTellerHome)ctx.lookup("ejb/TimeTellerEjbHttp"); TimeTeller teller = home.create(); System.out.println(teller.whatsTheTime());
Step 7. For HTTPS: Retrieve the JNDI proxy via the HTTPS, do the lookup and call your EJB which will communicate with the server via HTTPS as well.
Properties p = new Properties(); p.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.HttpNamingContextFactory"); p.put(Context.PROVIDER_URL, "https://localhost:8443/unified-invoker/SSLJNDIFactory/?return-exception=true"); p.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces"); ctx = new InitialContext(p); TimeTellerHome home = (TimeTellerHome)ctx.lookup("ejb/TimeTellerEjbHttps"); TimeTeller teller = home.create(); System.out.println(teller.whatsTheTime());
EJB3 Deployments Setup
Step 8. Configuring EJB3 beans with remote bindings can be done either via annotations or via XML. It is recommended that the XML option is chosen for a couple of reasons:
Bindings are linked to environmental setups and hence you wanna be decoupling those from the actual EJB source code.
Annotations do not allow ${jboss.bind.address} type of system property substitutions, which forces the user to hardcode the IP/hostname in the source code.
To use XML, create a jboss.xml file within the META-INF/ directory of the ejb jar deployment and add the following:
<?xml version="1.0" encoding="UTF-8"?> <jboss xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://www.jboss.org/j2ee/schema/jboss_5_0.xsd" version="3.0"> <enterprise-beans> <session> <ejb-name>TimeTellerBean</ejb-name> <remote-binding> <jndi-name>TimeTellerBean/http</jndi-name> <client-bind-url> http://${jboss.bind.address}:8080/unified-invoker/Ejb3ServerInvokerServlet/?return-exception=true </client-bind-url> </remote-binding> <remote-binding> <jndi-name>TimeTellerBean/https</jndi-name> <client-bind-url> https://${jboss.bind.address}:8443/unified-invoker/SSLEjb3ServerInvokerServlet/?return-exception=true </client-bind-url> </remote-binding> </session> </enterprise-beans> </jboss>
If you still wanna use annotations in spite of disadvantages mentioned above, add the following annotations to the EJB3 bean:
@RemoteBinding(jndiBinding="TimeTellerBean/http",clientBindUrl="http://localhost:8080/unified-invoker/Ejb3ServerInvokerServlet/?return-exception=true") @RemoteBinding(jndiBinding="TimeTellerBean/https",clientBindUrl="https://localhost:8443/unified-invoker/SSLEjb3ServerInvokerServlet/?return-exception=true")
EJB3 Client Code
Step 9. For HTTP: Retrieve the JNDI proxy via the HTTP, do the lookup and call your EJB which will communicate with the server via HTTP as well.
Properties p = new Properties(); p.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.HttpNamingContextFactory"); p.put(Context.PROVIDER_URL, "http://localhost:8080/unified-invoker/JNDIFactory/?return-exception=true"); p.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces"); ctx = new InitialContext(p); TimeTeller teller = (TimeTeller) ctx.lookup("TimeTellerBean/http"); System.out.println(teller.whatsTheTime(origin));
Step 10. For HTTPS: Retrieve the JNDI proxy via the HTTPS, do the lookup and call your EJB which will communicate with the server via HTTPS as well.
Properties p = new Properties(); p.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.HttpNamingContextFactory"); p.put(Context.PROVIDER_URL, "https://localhost:8443/unified-invoker/SSLJNDIFactory/?return-exception=true"); p.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces"); ctx = new InitialContext(p); TimeTeller teller = (TimeTeller) ctx.lookup("TimeTellerBean/https"); System.out.println(teller.whatsTheTime(origin));
JMS Client Code
Step 11. For HTTP: Retrieve the JNDI proxy via the HTTP and then lookup /ServletConnectionFactory so that you connect to the JMS server via the configured HTTP servlet transport.
Properties p = new Properties(); p.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.HttpNamingContextFactory"); p.put(Context.PROVIDER_URL, "http://localhost:8080/unified-invoker/JNDIFactory/?return-exception=true"); p.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces"); ic = new InitialContext(p); ConnectionFactory cf = (ConnectionFactory)ic.lookup("/ServletConnectionFactory"); Queue queue = (Queue)ic.lookup("queue/testQueue");
Step 12. For HTTPS: Retrieve the JNDI proxy via the HTTPS and then lookup /SSLServletConnectionFactory so that you connect to the JMS server via the configured HTTPS servlet transport.
Properties p = new Properties(); p.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.HttpNamingContextFactory"); p.put(Context.PROVIDER_URL, "http://localhost:8443/unified-invoker/JNDIFactory/?return-exception=true"); p.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces"); ic = new InitialContext(p); ConnectionFactory cf = (ConnectionFactory)ic.lookup("/SSLServletConnectionFactory"); Queue queue = (Queue)ic.lookup("queue/testQueue");
Debugging Notes
1. If you get the following exception or similar on the client side:
java.io.IOException: HTTPS hostname wrong: should be <localhost> at sun.net.www.protocol.https.HttpsClient.checkURLSpoofing(HttpsClient.java:490) at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:415)
Add the following to the client as well:
-Dorg.jboss.security.ignoreHttpsHost=true
2. If at some point during the testing the client throws a exception like this:
Caused by: java.io.StreamCorruptedException: invalid stream header at java.io.ObjectInputStream.readStreamHeader(ObjectInputStream.java:764) at java.io.ObjectInputStream.<init>(ObjectInputStream.java:277)
It quite likely means that the servlet that you're trying to access is the incorrect one. JBoss Remoting, as I indicated in JBREM-960 can throw this misleading exception when the servlet being accessed is the incorrect one, or you forgot to add the servlet mapping. You can use Linux command line tools like wget to debug access to servlets.
Warning
Please avoid the use of bind address 0.0.0.0 for a couple of reasons:
1. First of all, binding to 0.0.0.0 it's not recommended for security reasons. We strongly recommend that you bind to a specific IP/host.
2. Secondly, if binding to 0.0.0.0, upon deployment of unified-http-invoker.sar, AS/EAP will throw the following exception:
13:39:26,856 ERROR [ContainerBase] Servlet /unified-invoker threw load() exception javax.servlet.ServletException: Can not find servlet server invoker with same locator as specified (servlet://0.0.0.0:8080/unified-invoker/Ejb3ServerInvokerServlet) at org.jboss.remoting.transport.servlet.web.ServerInvokerServlet.getInvokerFromInvokerUrl(ServerInvokerServlet.java:198) at org.jboss.remoting.transport.servlet.web.ServerInvokerServlet.init(ServerInvokerServlet.java:66)
See JBREM-980. Until it gets fixed, please avoid binding to 0.0.0.0 if you're using the configuration above.
Comments