My job is to write kind of a webservice adapter using a Servlet which then calls an adapter EJB (SOAP webservice) which in turn calls the existing service methods (also EJBs). The existing architecture is based on EJB 2.0, which I can't change for now. External customers are supposed to access the Servlet and not the webservice class directly. The reason for this is that some pre-processing needs to be done using information in the HTTPRequest (fetching and mapping user-id from certificate resp. the HTTP Header). So, the Servlet reacts on the doPost() method, performs a preprocessing like parsing and marshalling the SOAP XML data and then calls the adapter EJB (stateless Session Bean), more precisely the webservice methods which will then trigger existing service methods in a different EJB. This works well so far until the EJBs require an existing Session resp. SessionContext, like in the following case where transaction rollback is handled:
protected void preventTransactionRolledBackException() {
if (this.getSessionContext().getRollbackOnly()) {
this.getSessionContext().setRollbackOnly();
}
}
In my setup, SessionContext is always null. As the whole application is already quite complex I try to post a simplified setup using only the Servlet and one EJB, hoping I'm not excluding something relevant. System: WebSphere Application Server 8.5
Servlet:
public class NewZekEclsServiceServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String operation = null;
response.setContentType("text/xml");
try {
PrintWriter out = response.getWriter();
// Get Header data
// Get Attribute data
// Get Body data:
InputStream body = request.getInputStream();
String xml = IOUtils.toString(body, "UTF-8");
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
docBuilderFactory.setNamespaceAware(true); // required as several namespaces might be used
DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
Document doc = docBuilder.parse(new InputSource(new StringReader(xml)));
operation = getOperation(doc); // method which scans the xml to retrieve the correct operation to use.
if (operation.equalsIgnoreCase("Ping")) {
// Calling the Adapter Bean:
EclsTestBeanBean eclsTestBean = new EclsTestBeanBean();
eclsTestBean.ejbCreate();
PingIn pingIn = new PingIn();
PingOut pingOut = eclsTestBean.ping(pingIn);
String xmlString = convertEclsObjectToSOAPString(pingOut);
out.print(xmlString);
}
}
}
}
EJB (generated by Rational Application Developer 9.0):
/**
* Bean implementation class for Session Bean: EclsTestBean
*
* @ejb.bean
* name="EclsTestBean"
* type="Stateless"
* jndi-name="ejb/ch/zek/ecls/EclsTestBeanHome"
* view-type="remote"
* transaction-type="Bean"
*
* @ejb.home
* remote-class="ch.zek.ecls.EclsTestBeanHome"
*
* @ejb.interface
* remote-class="ch.zek.ecls.EclsTestBean"
*
*/
public class EclsTestBeanBean implements javax.ejb.SessionBean {
private Log log = LogFactory.getLog(EcodeAbfragenAction.class);
private SessionContext mySessionCtx;
public SessionContext getSessionContext() {
return mySessionCtx;
}
public void setSessionContext(SessionContext ctx) {
mySessionCtx = ctx;
}
public void ejbCreate() throws CreateException {}
public void ejbActivate() {}
public void ejbPassivate() {}
public void ejbRemove() {}
/**
* Basic ping service for ECLS
* @param parameters
* @return
* @throws PingEntityNotFoundException
* @throws PingPermissionException
* @throws PingSystemException
*/
public ch.zek.ecls.PingOut ping(ch.zek.ecls.PingIn parameters) throws java.rmi.RemoteException, ch.zek.ecls.PingPermissionException, ch.zek.ecls.PingEntityNotFoundException, ch.zek.ecls.PingSystemException {
PingOut pingOut = new PingOut();
String pingAnswer = "Ping_ECLS_v1";
String adapter = "";
String operation = "";
Parameter[] msgParams = new Parameter[1];
String eclsEnvironment = "TEST";
pingAnswer += "_" + eclsEnvironment;
logAvailableEjbs();
try {
if (mySessionCtx != null) { // Why can it be null at all?
log.debug("mySessionCtx: " + mySessionCtx.getContextData());
} else {
log.debug("mySessionCtx was null");
InitialContext ic = new InitialContext();
mySessionCtx = (SessionContext) ic.lookup("java:comp/env/sessionContext"); // gives error: javax.naming.NameNotFoundException: Name "comp/env/sessionContext" not found in context "java:".
System.out.println("mySessionCtx: " + mySessionCtx);
}
} catch (Exception e) {
e.printStackTrace();
}
// creating the SOAP data... not relevant for the problem.
msgParams[0] = new Parameter();
msgParams[0].setValue(pingAnswer);
SystemMessage systemMessage = new SystemMessage();
systemMessage.setCode("OK");
systemMessage.setMessage("Ping");
systemMessage.setParameter(msgParams);
pingOut.setSystemMessage(systemMessage);
return pingOut;
}
/**
* ONLY USED DURING DEVELOPMENT:
* Helper method to print "accessible" EJBs.
*/
protected void logAvailableEjbs() {
try {
//Get the Initial Context for the JNDI lookup for a local EJB
InitialContext ic = new InitialContext();
NamingEnumeration<NameClassPair> list;
String level = "";
String name = "";
list = ic.list(level);
while (list.hasMore()) {
name = list.next().getName();
System.out.println(level + "/" + name);
}
level = "ejb";
list = ic.list(level);
while (list.hasMore()) {
name = list.next().getName();
System.out.println(level + "/" + name);
}
level = "java:comp";
list = ic.list(level);
while (list.hasMore()) {
name = list.next().getName();
System.out.println(level + "/" + name);
}
level = "java:comp/env";
list = ic.list(level);
while (list.hasMore()) {
name = list.next().getName();
System.out.println(level + "/" + name);
}
/*
level = "java:comp/env/ejb"; // Throws Error!
list = ic.list(level);
while (list.hasMore()) {
name = list.next().getName();
System.out.println(level + "/" + name);
}
*/
level = "java:global";
list = ic.list(level);
while (list.hasMore()) {
name = list.next().getName();
System.out.println(level + "/" + name);
}
} catch (NamingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
We are testing the setup using the SoapUI tool (http://www.soapui.org). Again, we get proper results for a simple Ping() service, but as soon as session-related stuff is required it fails.
Of course I did some research, especially interesting was this link: How to get SessionContext in JBOSS which also refers to: http://javahowto.blogspot.co.uk/2006/06/4-ways-to-get-ejbcontext-in-ejb-3.html
And regarding EJB lifecycle: docs.oracle.com/cd/E13224_01/wlw/docs100/guide/ejb/session/conSessionBeanLifeCycle.html J2EE-Tutorial: docs.oracle.com/javaee/6/tutorial/doc/gipss.html#gipsx (sorry, can't post more than 2 links...)
But as commented in the code, I can't access java:comp/env/ejb/sessionContext. Well... not even java:comp/env/ejb. I wrote a dirty helper method to see what is actually accessible to the EJB and get the following output:
SystemOut O /jta
SystemOut O /eis
SystemOut O /cell
SystemOut O /thisNode
SystemOut O /DefaultDatasource
SystemOut O /services
SystemOut O /jdbc
SystemOut O /servername
SystemOut O /com.ibm.websphere.ejbcontainer
SystemOut O /com
SystemOut O /zek
SystemOut O /wm
SystemOut O /ejb
SystemOut O /Increment
SystemOut O /tm
SystemOut O ejb/ivtEJBObject
SystemOut O ejb/ch
SystemOut O ejb/mgmt
SystemOut O java:comp/ValidatorFactory
SystemOut O java:comp/TransactionSynchronizationRegistry
SystemOut O java:comp/ORB
SystemOut O java:comp/Validator
SystemOut O java:comp/UserTransaction
SystemOut O java:comp/env
SystemOut O java:comp/BeanManager
SystemOut O java:comp/websphere
SystemOut O java:comp/HandleDelegate
SystemOut O java:global/com.ibm.ws.AppNameSpaces
SystemOut O java:global/NewZekEar
SystemOut O java:global/SchedulerCalendars
SystemOut O java:global/DefaultApplication
SystemOut O java:global/cell
SystemOut O java:global/query
SystemOut O java:global/ManagementEJB
SystemOut O java:global/ivtApp
I also tried to use annotation:
@Resource
private SessionContext mySessionCtx;
or setting the transaction type differently:
transaction-type="Container"
Nothing helped. I understood that the SessionContext should be automatically created. But if not - which option does one have to create a new one? Further question: The servlet also "creates" a session which I can access (request.getSession() ), but this is something different (HTTPSession). I guess I can't share or "convert" this session object to the session Bean?
You've instantiated the EclsTestBeanBean
yourself:
EclsTestBeanBean eclsTestBean = new EclsTestBeanBean();
EJB relies on wrapping the object in a proxy to allow the container to add extra services, including dependency injecting the session context, which means only the container can create them and give them out. You'll also find this object won't perform any of the EJB services, it's just a POJO here.
To request an EJB from the container, you need to either request it via dependency injection or look it up from JNDI, as per this answer:
JPA entity manager not correctly injected - Weblogic
Then the next problem will be that your bean doesn't seem to be available in JNDI - I think this is because you haven't defined a local or remote interface for it. That's fine in EJB3, where the "no-interface" view is used, but not in EJB2: http://help.eclipse.org/luna/index.jsp?topic=%2Forg.eclipse.jst.ejb.doc.user%2Ftopics%2Fcearch.html
So I'd suggest - write yourself an interface, (hopefully) then EclsTestBeanBean
then becomes available in JNDI, and look it up from there rather than instantiating it.
(With the @Resource annotation, be aware that all the annotation-based work was added in EJB3 as well.)