Showing posts with label websphere. Show all posts
Showing posts with label websphere. Show all posts

Tuesday, May 14, 2013

Invoking EJB3 in WebSphere using Spring


 Here are the steps to call a method of an EJB (v3.0) deployed in WebSphere 7 from a plain old Java client, either using Spring to shield your code from unnecessary implementation details, or bare-bones straightforward coding style.

Note: Tested against WAS 7 only.

Common Steps

1. Firstly, the Remote interface class is needed and will be referenced in your client code to call the EJB method. Copy the class file or Java source to your Java client workspace. In my example, the remote interface class is org.gizmo.minyakangin.CapKapakRemote
2. If you have a local development WebSphere 7, run the following command to create the stub class

[Class_Folder_Path]:>[WAS_HOME]\AppServer\bin\createEJBStubs.bat org.gizmo.minyakangin.CapKapakRemote -cp .

You should see an output similar to the one below:

Processing the org.gizmo.minyakangin.CapKapakRemote input file.
Command Successful

A file with the name _CapKapakRemote_Stub.class will be created under folder [Class_Folder_Path]\org\gizmo\minyakangin.

3. A missing stub file may produce the following errors when getting a reference to the EJB:

java.lang.ClassCastException: Unable to load class: org.gizmo.minyakangin._CapKapakRemote_Stub

java.lang.ClassCastException: org.omg.stub.java.rmi._Remote_Stub incompatible with org.gizmo.minyakangin.CapKapakRemote

4. At the WebSphere Admin Console, locate the EJB application and ensure a proper JNDI reference is assigned e.g. ejb/CapKapak. It is best not to rely on the default JNDI reference. But if you insist, the default is the fully qualified remote class name i.e. org.gizmo.minyakangin.CapKapakRemote.



5. Also, check the bootstrap port of the application server instance hosting the EJB (my environment is 9812):



6. At your Java client workspace, ensure that com.ibm.ws.webservices.thinclient_7.0.0.jar (under [WAS_HOME]\AppServer\runtimes) and com.ibm.ws.runtime.jar (under [WAS_HOME]\AppServer\plugins) are included in the classpath. Not sure why including only com.ibm.ws.ejb.thinclient_7.0.0.jar doesn't work in my case.

Caveat: The steps only work using IBM JDK (v7). You may need to do some 'hacking' to get it working under a different JVM.


Using Spring 3.x

You can interface with a plain Java interface (no EJB classes or annotations needed) using Spring. Here's the interface:


package org.gizmo.minyakangin;

public interface CapKapak {
	public String getBrand();
}



An EJB3 'bean' can be easily constructed using a JNDI object factory bean, and it's even easier to just use the 'jee' namespace, as below:




<beans xmlns:jee="http://www.springframework.org/schema/jee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemalocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/jee
        http://www.springframework.org/schema/jee/spring-jee-3.0.xsd">

	<jee:jndi-lookup id="capKapak" jndi-name="ejb/CapKapak">
		<jee:environment>
			java.naming.factory.initial=com.ibm.websphere.naming.WsnInitialContextFactory
			java.naming.provider.url=corbaloc:iiop:localhost:9812
		</jee:environment>
	</jee:jndi-lookup>

</beans>




Note: The 'java.naming.provider.url' value should refer to the WebSphere server's bootstrap address as shown above.

Then in the code (using Spring's JUnit class):


package org.gizmo.minyakangin;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
		"classpath:org/minyakangin/spring-context.xml"})
public class EjbTests extends AbstractJUnit4SpringContextTests {

	CapKapakRemote capKapak;
	
	@Before
	public void before() {
		capKapak = (CapKapakRemote)applicationContext.getBean("capKapak");
	}
	
	@Test
	public void testLoginManager() throws Exception {
		String brand = capKapak.getBrand();
		System.out.println(brand);
	}
}



Using plain Java



package org.gizmo.minyakangin;

import java.util.Properties;

import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;

import com.cv.ibs.user.session.LoginManagerRemote;
import com.cv.ibs.utils.SessionUser;

public class StandaloneEjbClient {

	public static void main (String args[]) throws Exception {
		Properties props = new Properties();
		props.put("java.naming.factory.initial", "com.ibm.websphere.naming.WsnInitialContextFactory");
		props.put("java.naming.provider.url", "corbaloc:iiop:localhost:9812");

		InitialContext ctx = new InitialContext(props);
		
		Object ejbBusIntf = ctx.lookup("ejb/CapKapak");
		CapKapakRemote loginManager = (CapKapakRemote)PortableRemoteObject.narrow(ejbBusIntf, CapKapakRemote.class);
		String brand = capKapak.getBrand();
		System.out.println(brand);
	}
}






Monday, December 12, 2011

Bad Certificate in WAS 5.1.0 (IBM JDK 1.4.1)

Once upon a time, there was a legacy web application system handling loans processing. It ran on Windows 2000 but used IBM WebSphere Application Server (WAS) 5.1.0 installation (yes, no fixpacks!). It ran well until recently when the system tried to access a remote URL using HTTPS/SSL, an error occurred. The log files were investigated and to the horrified faces of the support personnel...

javax.net.ssl.SSLHandshakeException: bad certificate

A few recommendations here and there led to them summoning me to have a look at this issue. At first I thought it was just a simple case of not importing the SSL certificates into the keystore. But after using ikeyman (WAS tool) to do the necessary, it still doesn't work. Ok, time to Google...

Version 5.1.0 of WAS runs on IBM JDK 1.4.1 (not even 1.4.2), which made matters worse. A quick search using Google yielded 2 most relevant results:

Problem is, both links didn't actually provide any solution, but they did nudge me a little to a workaround which I am documenting here right now.

This problem is not common if the WAS installations are patched up to at least v5.1.1, but due to the system's legacy status and most likely be replaced by another system early next year, there was no real incentive to patch it and 'hope for the best'. The personnel of the vendor supporting the system was also a blur case contract worker who spew common buzzwords yet lack any substance and logic.

So, what to do, what to do??? There were no source codes available (the vendor decided not to give an earlier contract worker his time off, so he retaliated by deleting all the source codes...a case of coder gone cuckoo/crazy). So, I decided to decompile the source codes using JD-GUI (http://java.decompiler.free.fr/?q=jdgui) after learning from the IT support girl on the URL that triggers the remote host handshaking. A quick look at the web.xml file and the servlet responsible for the remote request was identified.

After looking through the servlet code, I was able to identify the root cause and reconfirmed it by writing some JUnit tests:
  • Test #1 - Using Sun JDK1.4.2, no imported certs: Fail, "unknown certificate" (expected)
  • Test #2 - Using Sun JDK1.4.2, with imported certs: Pass (expected)
  • Test #3 - Using IBM JDK1.4.1 (similar to WAS 5.1.0), no imported certs: Fail (expected)
  • Test #4 - Using IBM JDK1.4.1 (similar to WAS 5.1.0), with imported certs: Fail, "bad certificate" (expected)

So the problem is the JDK, not the application code (sort of). IBM JDK1.4.1 is using IBM JSSE v1 (not even v2). I wanted to try using IBM JSSE v2 API files, but after looking through several Google results, no one was triumphant doing this.

To call the remote URL using HTTPS/SSL, Apache HttpClient API was used. A quick look at the online guide (http://hc.apache.org/httpclient-3.x/sslguide.html) gave me an idea to replace the default class to handle HTTPS connections with a custom class which can be coded to use Sun JSSE implementation. To do this, some class-overriding was necessary. The final code for the socket factory (I had to dig through the Sun JSSE classes to find the implementation class which actually is 'internal' and not recommended to be referenced externally, but desperate times require desperate measures):


import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;

import com.sun.net.ssl.internal.ssl.SSLSocketFactoryImpl;

public class SunJsseSslSocketFactory implements SecureProtocolSocketFactory {

 public Socket createSocket(String host, int port) 
  throws IOException, UnknownHostException
 {
  SSLSocketFactoryImpl sfi = new SSLSocketFactoryImpl();
  return sfi.createSocket(host, port);
 }

 public Socket createSocket(String host, int port, 
  InetAddress clientHost, int clientPort) 
   throws IOException, UnknownHostException
 {
  SSLSocketFactoryImpl sfi = new SSLSocketFactoryImpl();
  return sfi.createSocket(host, port, clientHost, clientPort);
 }

 public Socket createSocket(Socket socket, String host, 
  int port, boolean autoClose) 
   throws IOException, UnknownHostException
 {
  SSLSocketFactoryImpl sfi = new SSLSocketFactoryImpl();
  return sfi.createSocket(socket, host, port, autoClose);
 }

}


Then, to modify the HttpClient call to use this new socket factory class:

Protocol myHTTPS = new Protocol( "https", new SunJsseSslSocketFactory(), url.getPort() );

This line is placed before any calls to the remote URL, and only needed to be executed once.

2 other steps to do to complete the fix:
  • Place the Sun JSSE file (jsse.jar) into [WAS_HOME]/java/jre/lib/ext
  • Add the Sun JSSE provider into file [WAS_HOME]/java/jre/lib/security/java.security:

security.provider.6=com.sun.net.ssl.internal.ssl.Provider

In the end, the fix was applied to the production boxes and the remote call finally worked. All credit goes to the HttpClient API makers who at least coded some hooks for this customisation.