Source code: org/apache/axis/providers/java/EJBProvider.java
1 /*
2 * Copyright 2001-2004 The Apache Software Foundation.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.apache.axis.providers.java;
18
19 import java.lang.reflect.Method;
20 import java.lang.reflect.InvocationTargetException;
21 import java.util.Properties;
22
23 import javax.naming.Context;
24 import javax.naming.InitialContext;
25
26 import org.apache.axis.AxisFault;
27 import org.apache.axis.Constants;
28 import org.apache.axis.Handler;
29 import org.apache.axis.MessageContext;
30 import org.apache.axis.components.logger.LogFactory;
31 import org.apache.axis.handlers.soap.SOAPService;
32 import org.apache.axis.utils.ClassUtils;
33 import org.apache.axis.utils.Messages;
34 import org.apache.commons.logging.Log;
35
36 /**
37 * A basic EJB Provider
38 *
39 * @author Carl Woolf (cwoolf@macromedia.com)
40 * @author Tom Jordahl (tomj@macromedia.com)
41 * @author C?dric Chabanois (cchabanois@ifrance.com)
42 */
43 public class EJBProvider extends RPCProvider
44 {
45 protected static Log log =
46 LogFactory.getLog(EJBProvider.class.getName());
47
48 // The enterprise category is for stuff that an enterprise product might
49 // want to track, but in a simple environment (like the AXIS build) would
50 // be nothing more than a nuisance.
51 protected static Log entLog =
52 LogFactory.getLog(Constants.ENTERPRISE_LOG_CATEGORY);
53
54 public static final String OPTION_BEANNAME = "beanJndiName";
55 public static final String OPTION_HOMEINTERFACENAME = "homeInterfaceName";
56 public static final String OPTION_REMOTEINTERFACENAME = "remoteInterfaceName";
57 public static final String OPTION_LOCALHOMEINTERFACENAME = "localHomeInterfaceName";
58 public static final String OPTION_LOCALINTERFACENAME = "localInterfaceName";
59
60
61 public static final String jndiContextClass = "jndiContextClass";
62 public static final String jndiURL = "jndiURL";
63 public static final String jndiUsername = "jndiUser";
64 public static final String jndiPassword = "jndiPassword";
65
66 protected static final Class[] empty_class_array = new Class[0];
67 protected static final Object[] empty_object_array = new Object[0];
68
69 private static InitialContext cached_context = null;
70
71 ///////////////////////////////////////////////////////////////
72 ///////////////////////////////////////////////////////////////
73 /////// Default methods from JavaProvider ancestor, overridden
74 /////// for ejbeans
75 ///////////////////////////////////////////////////////////////
76 ///////////////////////////////////////////////////////////////
77
78 /**
79 * Return a object which implements the service.
80 *
81 * @param msgContext the message context
82 * @param clsName The JNDI name of the EJB home class
83 * @return an object that implements the service
84 */
85 protected Object makeNewServiceObject(MessageContext msgContext,
86 String clsName)
87 throws Exception
88 {
89 String remoteHomeName = getStrOption(OPTION_HOMEINTERFACENAME,
90 msgContext.getService());
91 String localHomeName = getStrOption(OPTION_LOCALHOMEINTERFACENAME,
92 msgContext.getService());
93 String homeName = (remoteHomeName != null ? remoteHomeName:localHomeName);
94
95 if (homeName == null) {
96 // cannot find both remote home and local home
97 throw new AxisFault(
98 Messages.getMessage("noOption00",
99 OPTION_HOMEINTERFACENAME,
100 msgContext.getTargetService()));
101 }
102
103 // Load the Home class name given in the config file
104 Class homeClass = ClassUtils.forName(homeName, true, msgContext.getClassLoader());
105
106 // we create either the ejb using either the RemoteHome or LocalHome object
107 if (remoteHomeName != null)
108 return createRemoteEJB(msgContext, clsName, homeClass);
109 else
110 return createLocalEJB(msgContext, clsName, homeClass);
111 }
112
113 /**
114 * Create an EJB using a remote home object
115 *
116 * @param msgContext the message context
117 * @param beanJndiName The JNDI name of the EJB remote home class
118 * @param homeClass the class of the home interface
119 * @return an EJB
120 */
121 private Object createRemoteEJB(MessageContext msgContext,
122 String beanJndiName,
123 Class homeClass)
124 throws Exception
125 {
126 // Get the EJB Home object from JNDI
127 Object ejbHome = getEJBHome(msgContext.getService(),
128 msgContext, beanJndiName);
129 Object ehome = javax.rmi.PortableRemoteObject.narrow(ejbHome, homeClass);
130
131 // Invoke the create method of the ejbHome class without actually
132 // touching any EJB classes (i.e. no cast to EJBHome)
133 Method createMethod = homeClass.getMethod("create", empty_class_array);
134 Object result = createMethod.invoke(ehome, empty_object_array);
135
136 return result;
137 }
138
139 /**
140 * Create an EJB using a local home object
141 *
142 * @param msgContext the message context
143 * @param beanJndiName The JNDI name of the EJB local home class
144 * @param homeClass the class of the home interface
145 * @return an EJB
146 */
147 private Object createLocalEJB(MessageContext msgContext,
148 String beanJndiName,
149 Class homeClass)
150 throws Exception
151 {
152 // Get the EJB Home object from JNDI
153 Object ejbHome = getEJBHome(msgContext.getService(),
154 msgContext, beanJndiName);
155
156 // the home object is a local home object
157 Object ehome;
158 if (homeClass.isInstance(ejbHome))
159 ehome = ejbHome;
160 else
161 throw new ClassCastException(
162 Messages.getMessage("badEjbHomeType"));
163
164 // Invoke the create method of the ejbHome class without actually
165 // touching any EJB classes (i.e. no cast to EJBLocalHome)
166 Method createMethod = homeClass.getMethod("create", empty_class_array);
167 Object result = createMethod.invoke(ehome, empty_object_array);
168
169 return result;
170 }
171
172 /**
173 * Tells if the ejb that will be used to handle this service is a remote
174 * one
175 */
176 private boolean isRemoteEjb(SOAPService service)
177 {
178 return getStrOption(OPTION_HOMEINTERFACENAME,service) != null;
179 }
180
181 /**
182 * Tells if the ejb that will be used to handle this service is a local
183 * one
184 */
185 private boolean isLocalEjb(SOAPService service)
186 {
187 return (!isRemoteEjb(service)) &&
188 (getStrOption(OPTION_LOCALHOMEINTERFACENAME,service) != null);
189 }
190
191
192 /**
193 * Return the option in the configuration that contains the service class
194 * name. In the EJB case, it is the JNDI name of the bean.
195 */
196 protected String getServiceClassNameOptionName()
197 {
198 return OPTION_BEANNAME;
199 }
200
201 /**
202 * Get a String option by looking first in the service options,
203 * and then at the Handler's options. This allows defaults to be
204 * specified at the provider level, and then overriden for particular
205 * services.
206 *
207 * @param optionName the option to retrieve
208 * @return String the value of the option or null if not found in
209 * either scope
210 */
211 protected String getStrOption(String optionName, Handler service)
212 {
213 String value = null;
214 if (service != null)
215 value = (String)service.getOption(optionName);
216 if (value == null)
217 value = (String)getOption(optionName);
218 return value;
219 }
220
221 /**
222 * Get the remote interface of an ejb from its home class.
223 * This function can only be used for remote ejbs
224 *
225 * @param beanJndiName the jndi name of the ejb
226 * @param service the soap service
227 * @param msgContext the message context (can be null)
228 */
229 private Class getRemoteInterfaceClassFromHome(String beanJndiName,
230 SOAPService service,
231 MessageContext msgContext)
232 throws Exception
233 {
234 // Get the EJB Home object from JNDI
235 Object ejbHome = getEJBHome(service, msgContext, beanJndiName);
236
237 String homeName = getStrOption(OPTION_HOMEINTERFACENAME,
238 service);
239 if (homeName == null)
240 throw new AxisFault(
241 Messages.getMessage("noOption00",
242 OPTION_HOMEINTERFACENAME,
243 service.getName()));
244
245 // Load the Home class name given in the config file
246 ClassLoader cl = (msgContext != null) ?
247 msgContext.getClassLoader() :
248 Thread.currentThread().getContextClassLoader();
249 Class homeClass = ClassUtils.forName(homeName, true, cl);
250
251
252 // Make sure the object we got back from JNDI is the same type
253 // as the what is specified in the config file
254 Object ehome = javax.rmi.PortableRemoteObject.narrow(ejbHome, homeClass);
255
256 // This code requires the use of ejb.jar, so we do the stuff below
257 // EJBHome ejbHome = (EJBHome) ehome;
258 // EJBMetaData meta = ejbHome.getEJBMetaData();
259 // Class interfaceClass = meta.getRemoteInterfaceClass();
260
261 // Invoke the getEJBMetaData method of the ejbHome class without
262 // actually touching any EJB classes (i.e. no cast to EJBHome)
263 Method getEJBMetaData =
264 homeClass.getMethod("getEJBMetaData", empty_class_array);
265 Object metaData = getEJBMetaData.invoke(ehome, empty_object_array);
266 Method getRemoteInterfaceClass =
267 metaData.getClass().getMethod("getRemoteInterfaceClass",
268 empty_class_array);
269 return (Class) getRemoteInterfaceClass.invoke(metaData,
270 empty_object_array);
271 }
272
273
274 /**
275 * Get the class description for the EJB Remote or Local Interface,
276 * which is what we are interested in exposing to the world (i.e. in WSDL).
277 *
278 * @param msgContext the message context (can be null)
279 * @param beanJndiName the JNDI name of the EJB
280 * @return the class info of the EJB remote or local interface
281 */
282 protected Class getServiceClass(String beanJndiName,
283 SOAPService service,
284 MessageContext msgContext)
285 throws AxisFault
286 {
287 Class interfaceClass = null;
288
289 try {
290 // First try to get the interface class from the configuation
291 // Note that we don't verify that remote remoteInterfaceName is used for
292 // remote ejb and localInterfaceName for local ejb. Should we ?
293 String remoteInterfaceName =
294 getStrOption(OPTION_REMOTEINTERFACENAME, service);
295 String localInterfaceName =
296 getStrOption(OPTION_LOCALINTERFACENAME, service);
297 String interfaceName = (remoteInterfaceName != null ? remoteInterfaceName : localInterfaceName);
298
299 if(interfaceName != null){
300 ClassLoader cl = (msgContext != null) ?
301 msgContext.getClassLoader() :
302 Thread.currentThread().getContextClassLoader();
303 interfaceClass = ClassUtils.forName(interfaceName,
304 true,
305 cl);
306 }
307 else
308 {
309 // cannot get the interface name from the configuration, we get
310 // it from the EJB Home (if remote)
311 if (isRemoteEjb(service)) {
312 interfaceClass = getRemoteInterfaceClassFromHome(beanJndiName,
313 service,
314 msgContext);
315 }
316 else
317 if (isLocalEjb(service)) {
318 // we cannot get the local interface from the local ejb home
319 // localInterfaceName is mandatory for local ejbs
320 throw new AxisFault(
321 Messages.getMessage("noOption00",
322 OPTION_LOCALINTERFACENAME,
323 service.getName()));
324 }
325 else
326 {
327 // neither a local ejb or a remote one ...
328 throw new AxisFault(Messages.getMessage("noOption00",
329 OPTION_HOMEINTERFACENAME,
330 service.getName()));
331 }
332 }
333 } catch (Exception e) {
334 throw AxisFault.makeFault(e);
335 }
336
337 // got it, return it
338 return interfaceClass;
339 }
340
341 /**
342 * Common routine to do the JNDI lookup on the Home interface object
343 * username and password for jndi lookup are got from the configuration or from
344 * the messageContext if not found in the configuration
345 */
346 private Object getEJBHome(SOAPService serviceHandler,
347 MessageContext msgContext,
348 String beanJndiName)
349 throws AxisFault
350 {
351 Object ejbHome = null;
352
353 // Set up an InitialContext and use it get the beanJndiName from JNDI
354 try {
355 Properties properties = null;
356
357 // collect all the properties we need to access JNDI:
358 // username, password, factoryclass, contextUrl
359
360 // username
361 String username = getStrOption(jndiUsername, serviceHandler);
362 if ((username == null) && (msgContext != null))
363 username = msgContext.getUsername();
364 if (username != null) {
365 if (properties == null)
366 properties = new Properties();
367 properties.setProperty(Context.SECURITY_PRINCIPAL, username);
368 }
369
370 // password
371 String password = getStrOption(jndiPassword, serviceHandler);
372 if ((password == null) && (msgContext != null))
373 password = msgContext.getPassword();
374 if (password != null) {
375 if (properties == null)
376 properties = new Properties();
377 properties.setProperty(Context.SECURITY_CREDENTIALS, password);
378 }
379
380 // factory class
381 String factoryClass = getStrOption(jndiContextClass, serviceHandler);
382 if (factoryClass != null) {
383 if (properties == null)
384 properties = new Properties();
385 properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, factoryClass);
386 }
387
388 // contextUrl
389 String contextUrl = getStrOption(jndiURL, serviceHandler);
390 if (contextUrl != null) {
391 if (properties == null)
392 properties = new Properties();
393 properties.setProperty(Context.PROVIDER_URL, contextUrl);
394 }
395
396 // get context using these properties
397 InitialContext context = getContext(properties);
398
399 // if we didn't get a context, fail
400 if (context == null)
401 throw new AxisFault( Messages.getMessage("cannotCreateInitialContext00"));
402
403 ejbHome = getEJBHome(context, beanJndiName);
404
405 if (ejbHome == null)
406 throw new AxisFault( Messages.getMessage("cannotFindJNDIHome00",beanJndiName));
407 }
408 // Should probably catch javax.naming.NameNotFoundException here
409 catch (Exception exception) {
410 entLog.info(Messages.getMessage("toAxisFault00"), exception);
411 throw AxisFault.makeFault(exception);
412 }
413
414 return ejbHome;
415 }
416
417 protected InitialContext getCachedContext()
418 throws javax.naming.NamingException
419 {
420 if (cached_context == null)
421 cached_context = new InitialContext();
422 return cached_context;
423 }
424
425
426 protected InitialContext getContext(Properties properties)
427 throws AxisFault, javax.naming.NamingException
428 {
429 // if we got any stuff from the configuration file
430 // create a new context using these properties
431 // otherwise, we get a default context and cache it for next time
432 return ((properties == null)
433 ? getCachedContext()
434 : new InitialContext(properties));
435 }
436
437 protected Object getEJBHome(InitialContext context, String beanJndiName)
438 throws AxisFault, javax.naming.NamingException
439 {
440 // Do the JNDI lookup
441 return context.lookup(beanJndiName);
442 }
443
444 /**
445 * Override the default implementation such that we can include
446 * special handling for {@link java.rmi.ServerException}.
447 * <p/>
448 * Converts {@link java.rmi.ServerException} exceptions to
449 * {@link InvocationTargetException} exceptions with the same cause.
450 * This allows the axis framework to create a SOAP fault.
451 * </p>
452 *
453 * @see org.apache.axis.providers.java.RPCProvider#invokeMethod(org.apache.axis.MessageContext, java.lang.reflect.Method, java.lang.Object, java.lang.Object[])
454 */
455 protected Object invokeMethod(MessageContext msgContext, Method method,
456 Object obj, Object[] argValues)
457 throws Exception {
458 try {
459 return super.invokeMethod(msgContext, method, obj, argValues);
460 } catch (InvocationTargetException ite) {
461 Throwable cause = getCause(ite);
462 if (cause instanceof java.rmi.ServerException) {
463 throw new InvocationTargetException(getCause(cause));
464 }
465 throw ite;
466 }
467 }
468
469 /**
470 * Get the cause of an exception, using reflection so that
471 * it still works under JDK 1.3
472 *
473 * @param original the original exception
474 * @return the cause of the exception, or the given exception if the cause cannot be discovered.
475 */
476 private Throwable getCause(Throwable original) {
477 try {
478 Method method = original.getClass().getMethod("getCause", null);
479 Throwable cause = (Throwable) method.invoke(original, null);
480 if (cause != null) {
481 return cause;
482 }
483 } catch (NoSuchMethodException nsme) {
484 // ignore, this occurs under JDK 1.3
485 } catch (Throwable t) {
486 }
487 return original;
488 }
489 }