1 /*
2 * JBoss, the OpenSource J2EE webOS
3 *
4 * Distributable under LGPL license.
5 * See terms of license at gnu.org.
6 */
7
8 // $Id: EJBProvider.java,v 1.3.4.4 2003/03/28 12:50:46 cgjung Exp $
9
10 package org.jboss.net.axis.server;
11
12 import java.lang.reflect.Method;
13 import java.util.ArrayList;
14 import java.util.Iterator;
15
16 import javax.naming.NamingException;
17 import javax.servlet.http.HttpSessionBindingEvent;
18 import javax.servlet.http.HttpSessionBindingListener;
19 import javax.xml.namespace.QName;
20 import javax.xml.rpc.server.ServiceLifecycle;
21
22 import org.apache.axis.AxisFault;
23 import org.apache.axis.EngineConfiguration;
24 import org.apache.axis.MessageContext;
25 import org.apache.axis.description.OperationDesc;
26 import org.apache.axis.description.ServiceDesc;
27 import org.apache.axis.handlers.soap.SOAPService;
28 import org.apache.axis.message.SOAPEnvelope;
29
30 import org.jboss.net.axis.XMLResourceProvider;
31
32 /**
33 * <p>
34 * A JBoss-compatible EJB Provider that exposes the methods of
35 * any session bean as a web service endpoint.
36 * </p>
37 * <p>
38 * Basically it is a slimmed downed derivative of
39 * the Axis-EJBProvider without the usual, corba-related configuration mumbo-jumbo
40 * that is operating under the presumption that the right classloader has already been set
41 * by the request flow chain (@see org.jboss.net.axis.SetClassLoaderHandler).
42 * </p>
43 * <p>
44 * Since Version 1.5 and thanks to Kevin Conner, we now also support
45 * stateful beans that are tied to the service scope (you should reasonably
46 * choose scope="session" in the <service/> tag of the corresponding web-service.xml)
47 * Note that by using a jaxp lifecycle handler we synchronize with
48 * web service scopes that can be shorter-lived than the surrounding http-session.
49 * However, as I understood Kevin and from my observations, it seems that Axis
50 * and hence the jaxp lifecycle does not get notified upon http-session expiration.
51 * Hence our lifecycle listener currently implements the http session
52 * lifecycle, too (which is not very transport-agnostic, but who cares
53 * at the moment).
54 * </p>
55 * <p>
56 * EJBProvider is able to recognize an {@link WsdlAwareHttpActionHandler} in its
57 * transport chain such that it will set the soap-action headers in the wsdl.
58 * </p>
59 * @author <a href="mailto:Christoph.Jung@infor.de">Christoph G. Jung</a>
60 * @since 5. Oktober 2001, 13:02
61 * @version $Revision: 1.3.4.4 $
62 */
63
64 public class EJBProvider extends org.apache.axis.providers.java.EJBProvider {
65
66 //
67 // Attributes
68 //
69
70 /** the real remote class we are shielding */
71 protected Class remoteClass;
72
73 /** we are caching the home for perfomance purposes */
74 protected Object ejbHome;
75
76 /** we are caching the create method for perfomance purposes */
77 protected Method ejbCreateMethod;
78
79 //
80 // Constructors
81 //
82
83 /** Creates new EJBProvider */
84 public EJBProvider() {
85 }
86
87 //
88 // Helper Methods
89 //
90
91 /**
92 * access home factory via jndi if
93 * reference is not yet cached
94 */
95
96 protected synchronized Object getEJBHome(String jndiName)
97 throws NamingException {
98 if (ejbHome == null) {
99 // Get the EJB Home object from JNDI
100 ejbHome = this.getCachedContext().lookup(jndiName);
101 }
102 return ejbHome;
103 }
104
105 /**
106 * access home factory via jndi if
107 * reference is not yet cached
108 */
109
110 protected synchronized Method getEJBCreateMethod(String jndiName)
111 throws NamingException, NoSuchMethodException {
112 if (ejbCreateMethod == null) {
113 Object ejbHome = getEJBHome(jndiName);
114 ejbCreateMethod =
115 ejbHome.getClass().getMethod("create", empty_class_array);
116 }
117
118 return ejbCreateMethod;
119 }
120
121 /**
122 * Return the object which implements the service lifecycle. Makes the usual
123 * lookup->create call w/o the PortableRemoteDaDaDa for the sake of Corba.
124 * @param msgContext the message context
125 * @param clsName The JNDI name of the EJB home class
126 * @return an object that implements the service
127 */
128 protected Object makeNewServiceObject(
129 MessageContext msgContext,
130 String clsName)
131 throws Exception {
132
133 // abuse the clsName as jndi lookup name
134 Object ejbHome = getEJBHome(clsName);
135 // Invoke the create method of the ejbHome class without actually
136 // touching any EJB classes (i.e. no cast to EJBHome)
137 Method createMethod = getEJBCreateMethod(clsName);
138 // shield behind the lifecycle service
139 return new EJBServiceLifeCycle(
140 createMethod.invoke(ejbHome, empty_object_array));
141 }
142
143 /**
144 * Return the class name of the service, note that this could be called
145 * outside the correct chain, e.g., by the url mapper. Hence we need
146 * to find the right classloader.
147 */
148
149 protected synchronized Class getServiceClass(
150 String beanJndiName,
151 SOAPService service,
152 MessageContext msgContext)
153 throws AxisFault {
154 if (remoteClass == null) {
155
156 // we need to find the right service classloader first
157 // through the engine
158 EngineConfiguration engineConfig =
159 msgContext != null ? msgContext.getAxisEngine().getConfig() : null;
160
161 ClassLoader currentLoader =
162 Thread.currentThread().getContextClassLoader();
163
164 if (engineConfig != null
165 && engineConfig instanceof XMLResourceProvider) {
166 XMLResourceProvider config = (XMLResourceProvider) engineConfig;
167
168 ClassLoader newLoader =
169 config.getMyDeployment().getClassLoader(
170 new QName(null, service.getName()));
171
172 // enter the classloader
173 Thread.currentThread().setContextClassLoader(newLoader);
174 }
175
176 try {
177 remoteClass = getEJBCreateMethod(beanJndiName).getReturnType();
178 } catch (NamingException e) {
179 throw new AxisFault("Could not find home in JNDI", e);
180 } catch (NoSuchMethodException e) {
181 throw new AxisFault("Could not find create method at home ;-)", e);
182 } finally {
183 Thread.currentThread().setContextClassLoader(currentLoader);
184 }
185
186 }
187
188 return remoteClass;
189 }
190
191 //
192 // Public API
193 //
194
195 /**
196 * Generate the WSDL for this service.
197 * We need to rearrange the classloader stuff for that purpose similar
198 * as we did with the service class interface
199 */
200
201 public void generateWSDL(MessageContext msgContext) throws AxisFault {
202 EngineConfiguration engineConfig =
203 msgContext != null ? msgContext.getAxisEngine().getConfig() : null;
204
205 ClassLoader currentLoader =
206 Thread.currentThread().getContextClassLoader();
207
208 if (engineConfig != null
209 && engineConfig instanceof XMLResourceProvider) {
210 XMLResourceProvider config = (XMLResourceProvider) engineConfig;
211 ClassLoader newLoader =
212 config.getMyDeployment().getClassLoader(
213 new QName(null, msgContext.getTargetService()));
214 Thread.currentThread().setContextClassLoader(newLoader);
215 }
216
217 try {
218
219 // check whether there is an http action header present
220 if (msgContext != null) {
221
222 boolean isSoapAction =
223 msgContext.getProperty(
224 Constants.ACTION_HANDLER_PRESENT_PROPERTY)
225 == Boolean.TRUE;
226
227 // yes, then loop through the operation descriptions
228 for (Iterator alloperations =
229 msgContext
230 .getService()
231 .getServiceDescription()
232 .getOperations()
233 .iterator();
234 alloperations.hasNext();
235 ) {
236 OperationDesc opDesc = (OperationDesc) alloperations.next();
237 // and add a soap action tag with the name of the service
238 opDesc.setSoapAction(
239 isSoapAction ? msgContext.getService().getName() : null);
240 }
241 }
242
243 super.generateWSDL(msgContext);
244
245 } finally {
246 Thread.currentThread().setContextClassLoader(currentLoader);
247 }
248 }
249
250 /**
251 * Override processMessage of super class in order
252 * to unpack the service object from the lifecycle
253 */
254 public void processMessage(
255 MessageContext msgContext,
256 SOAPEnvelope reqEnv,
257 SOAPEnvelope resEnv,
258 Object obj)
259 throws Exception {
260 super.processMessage(
261 msgContext,
262 reqEnv,
263 resEnv,
264 ((EJBServiceLifeCycle) obj).serviceObject);
265 }
266
267 /*
268 * Adds the correct stop classes and soap-action annotations to wsdl generation
269 * @see org.apache.axis.providers.java.EJBProvider#initServiceDesc(org.apache.axis.handlers.soap.SOAPService,org.apache.axis.MessageContext)
270 */
271 public void initServiceDesc(SOAPService service, MessageContext msgContext)
272 throws AxisFault {
273 // the service class used to fill service description is the EJB Remote/Local Interface
274 // we add EJBObject and EJBLocalObject as stop classes because we
275 // don't want any of their methods in the wsdl ...
276 ServiceDesc serviceDescription = service.getServiceDescription();
277 ArrayList stopClasses = serviceDescription.getStopClasses();
278 if (stopClasses == null)
279 stopClasses = new ArrayList();
280 stopClasses.add("javax.ejb.EJBObject");
281 stopClasses.add("javax.ejb.EJBLocalObject");
282 serviceDescription.setStopClasses(stopClasses);
283 // once the stop classes are right, we can generate meta-data for only
284 // the wanted methods
285 super.initServiceDesc(service, msgContext);
286 }
287
288 //
289 // Inner Classes
290 //
291
292 /**
293 * This is the lifecycle object that is registered in the
294 * message scope and that shields the proper bean reference
295 */
296
297 protected static class EJBServiceLifeCycle
298 implements ServiceLifecycle, HttpSessionBindingListener {
299
300 //
301 // Attributes
302 //
303
304 /** may be local or remote object */
305 protected Object serviceObject;
306
307 //
308 // Constructors
309 //
310
311 /** constructs a new lifecycle */
312 protected EJBServiceLifeCycle(Object serviceObject) {
313 this.serviceObject = serviceObject;
314 }
315
316 //
317 // Public API
318 //
319
320 /**
321 * call remove
322 * @see javax.xml.rpc.server.ServiceLifecycle#destroy()
323 */
324 public void destroy() {
325 try {
326 if (serviceObject instanceof javax.ejb.EJBObject) {
327 try {
328 ((javax.ejb.EJBObject) serviceObject).remove();
329 } catch (java.rmi.RemoteException e) {
330 // have yet to decide what to do
331 }
332 } else {
333 ((javax.ejb.EJBLocalObject) serviceObject).remove();
334 }
335 } catch (javax.ejb.RemoveException e) {
336 // have yet to decide what to do
337 }
338 }
339
340 /**
341 * Nothing to be done
342 * @see javax.xml.rpc.server.ServiceLifecycle#init(Object)
343 */
344 public void init(Object arg0) {
345 }
346
347 /**
348 * @see javax.servlet.http.HttpSessionBindingListener#valueBound(HttpSessionBindingEvent)
349 */
350 public void valueBound(HttpSessionBindingEvent arg0) {
351 init(arg0);
352 }
353
354 /**
355 * @see javax.servlet.http.HttpSessionBindingListener#valueUnbound(HttpSessionBindingEvent)
356 */
357 public void valueUnbound(HttpSessionBindingEvent arg0) {
358 destroy();
359 }
360
361 } // EJBServiceLifeCycle
362
363 } // EJBProvider