/*
* Copyright 2001-2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.axis.providers.java;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.InitialContext;
import org.apache.axis.AxisFault;
import org.apache.axis.Constants;
import org.apache.axis.Handler;
import org.apache.axis.MessageContext;
import org.apache.axis.components.logger.LogFactory;
import org.apache.axis.handlers.soap.SOAPService;
import org.apache.axis.utils.ClassUtils;
import org.apache.axis.utils.Messages;
import org.apache.commons.logging.Log;
/**
* A basic EJB Provider
*
* @author Carl Woolf (cwoolf@macromedia.com)
* @author Tom Jordahl (tomj@macromedia.com)
* @author C?dric Chabanois (cchabanois@ifrance.com)
*/
public class EJBProvider extends RPCProvider
{
protected static Log log =
LogFactory.getLog(EJBProvider.class.getName());
// The enterprise category is for stuff that an enterprise product might
// want to track, but in a simple environment (like the AXIS build) would
// be nothing more than a nuisance.
protected static Log entLog =
LogFactory.getLog(Constants.ENTERPRISE_LOG_CATEGORY);
public static final String OPTION_BEANNAME = "beanJndiName";
public static final String OPTION_HOMEINTERFACENAME = "homeInterfaceName";
public static final String OPTION_REMOTEINTERFACENAME = "remoteInterfaceName";
public static final String OPTION_LOCALHOMEINTERFACENAME = "localHomeInterfaceName";
public static final String OPTION_LOCALINTERFACENAME = "localInterfaceName";
public static final String jndiContextClass = "jndiContextClass";
public static final String jndiURL = "jndiURL";
public static final String jndiUsername = "jndiUser";
public static final String jndiPassword = "jndiPassword";
protected static final Class[] empty_class_array = new Class[0];
protected static final Object[] empty_object_array = new Object[0];
private static InitialContext cached_context = null;
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
/////// Default methods from JavaProvider ancestor, overridden
/////// for ejbeans
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
/**
* Return a object which implements the service.
*
* @param msgContext the message context
* @param clsName The JNDI name of the EJB home class
* @return an object that implements the service
*/
protected Object makeNewServiceObject(MessageContext msgContext,
String clsName)
throws Exception
{
String remoteHomeName = getStrOption(OPTION_HOMEINTERFACENAME,
msgContext.getService());
String localHomeName = getStrOption(OPTION_LOCALHOMEINTERFACENAME,
msgContext.getService());
String homeName = (remoteHomeName != null ? remoteHomeName:localHomeName);
if (homeName == null) {
// cannot find both remote home and local home
throw new AxisFault(
Messages.getMessage("noOption00",
OPTION_HOMEINTERFACENAME,
msgContext.getTargetService()));
}
// Load the Home class name given in the config file
Class homeClass = ClassUtils.forName(homeName, true, msgContext.getClassLoader());
// we create either the ejb using either the RemoteHome or LocalHome object
if (remoteHomeName != null)
return createRemoteEJB(msgContext, clsName, homeClass);
else
return createLocalEJB(msgContext, clsName, homeClass);
}
/**
* Create an EJB using a remote home object
*
* @param msgContext the message context
* @param beanJndiName The JNDI name of the EJB remote home class
* @param homeClass the class of the home interface
* @return an EJB
*/
private Object createRemoteEJB(MessageContext msgContext,
String beanJndiName,
Class homeClass)
throws Exception
{
// Get the EJB Home object from JNDI
Object ejbHome = getEJBHome(msgContext.getService(),
msgContext, beanJndiName);
Object ehome = javax.rmi.PortableRemoteObject.narrow(ejbHome, homeClass);
// Invoke the create method of the ejbHome class without actually
// touching any EJB classes (i.e. no cast to EJBHome)
Method createMethod = homeClass.getMethod("create", empty_class_array);
Object result = createMethod.invoke(ehome, empty_object_array);
return result;
}
/**
* Create an EJB using a local home object
*
* @param msgContext the message context
* @param beanJndiName The JNDI name of the EJB local home class
* @param homeClass the class of the home interface
* @return an EJB
*/
private Object createLocalEJB(MessageContext msgContext,
String beanJndiName,
Class homeClass)
throws Exception
{
// Get the EJB Home object from JNDI
Object ejbHome = getEJBHome(msgContext.getService(),
msgContext, beanJndiName);
// the home object is a local home object
Object ehome;
if (homeClass.isInstance(ejbHome))
ehome = ejbHome;
else
throw new ClassCastException(
Messages.getMessage("badEjbHomeType"));
// Invoke the create method of the ejbHome class without actually
// touching any EJB classes (i.e. no cast to EJBLocalHome)
Method createMethod = homeClass.getMethod("create", empty_class_array);
Object result = createMethod.invoke(ehome, empty_object_array);
return result;
}
/**
* Tells if the ejb that will be used to handle this service is a remote
* one
*/
private boolean isRemoteEjb(SOAPService service)
{
return getStrOption(OPTION_HOMEINTERFACENAME,service) != null;
}
/**
* Tells if the ejb that will be used to handle this service is a local
* one
*/
private boolean isLocalEjb(SOAPService service)
{
return (!isRemoteEjb(service)) &&
(getStrOption(OPTION_LOCALHOMEINTERFACENAME,service) != null);
}
/**
* Return the option in the configuration that contains the service class
* name. In the EJB case, it is the JNDI name of the bean.
*/
protected String getServiceClassNameOptionName()
{
return OPTION_BEANNAME;
}
/**
* Get a String option by looking first in the service options,
* and then at the Handler's options. This allows defaults to be
* specified at the provider level, and then overriden for particular
* services.
*
* @param optionName the option to retrieve
* @return String the value of the option or null if not found in
* either scope
*/
protected String getStrOption(String optionName, Handler service)
{
String value = null;
if (service != null)
value = (String)service.getOption(optionName);
if (value == null)
value = (String)getOption(optionName);
return value;
}
/**
* Get the remote interface of an ejb from its home class.
* This function can only be used for remote ejbs
*
* @param beanJndiName the jndi name of the ejb
* @param service the soap service
* @param msgContext the message context (can be null)
*/
private Class getRemoteInterfaceClassFromHome(String beanJndiName,
SOAPService service,
MessageContext msgContext)
throws Exception
{
// Get the EJB Home object from JNDI
Object ejbHome = getEJBHome(service, msgContext, beanJndiName);
String homeName = getStrOption(OPTION_HOMEINTERFACENAME,
service);
if (homeName == null)
throw new AxisFault(
Messages.getMessage("noOption00",
OPTION_HOMEINTERFACENAME,
service.getName()));
// Load the Home class name given in the config file
ClassLoader cl = (msgContext != null) ?
msgContext.getClassLoader() :
Thread.currentThread().getContextClassLoader();
Class homeClass = ClassUtils.forName(homeName, true, cl);
// Make sure the object we got back from JNDI is the same type
// as the what is specified in the config file
Object ehome = javax.rmi.PortableRemoteObject.narrow(ejbHome, homeClass);
// This code requires the use of ejb.jar, so we do the stuff below
// EJBHome ejbHome = (EJBHome) ehome;
// EJBMetaData meta = ejbHome.getEJBMetaData();
// Class interfaceClass = meta.getRemoteInterfaceClass();
// Invoke the getEJBMetaData method of the ejbHome class without
// actually touching any EJB classes (i.e. no cast to EJBHome)
Method getEJBMetaData =
homeClass.getMethod("getEJBMetaData", empty_class_array);
Object metaData = getEJBMetaData.invoke(ehome, empty_object_array);
Method getRemoteInterfaceClass =
metaData.getClass().getMethod("getRemoteInterfaceClass",
empty_class_array);
return (Class) getRemoteInterfaceClass.invoke(metaData,
empty_object_array);
}
/**
* Get the class description for the EJB Remote or Local Interface,
* which is what we are interested in exposing to the world (i.e. in WSDL).
*
* @param msgContext the message context (can be null)
* @param beanJndiName the JNDI name of the EJB
* @return the class info of the EJB remote or local interface
*/
protected Class getServiceClass(String beanJndiName,
SOAPService service,
MessageContext msgContext)
throws AxisFault
{
Class interfaceClass = null;
try {
// First try to get the interface class from the configuation
// Note that we don't verify that remote remoteInterfaceName is used for
// remote ejb and localInterfaceName for local ejb. Should we ?
String remoteInterfaceName =
getStrOption(OPTION_REMOTEINTERFACENAME, service);
String localInterfaceName =
getStrOption(OPTION_LOCALINTERFACENAME, service);
String interfaceName = (remoteInterfaceName != null ? remoteInterfaceName : localInterfaceName);
if(interfaceName != null){
ClassLoader cl = (msgContext != null) ?
msgContext.getClassLoader() :
Thread.currentThread().getContextClassLoader();
interfaceClass = ClassUtils.forName(interfaceName,
true,
cl);
}
else
{
// cannot get the interface name from the configuration, we get
// it from the EJB Home (if remote)
if (isRemoteEjb(service)) {
interfaceClass = getRemoteInterfaceClassFromHome(beanJndiName,
service,
msgContext);
}
else
if (isLocalEjb(service)) {
// we cannot get the local interface from the local ejb home
// localInterfaceName is mandatory for local ejbs
throw new AxisFault(
Messages.getMessage("noOption00",
OPTION_LOCALINTERFACENAME,
service.getName()));
}
else
{
// neither a local ejb or a remote one ...
throw new AxisFault(Messages.getMessage("noOption00",
OPTION_HOMEINTERFACENAME,
service.getName()));
}
}
} catch (Exception e) {
throw AxisFault.makeFault(e);
}
// got it, return it
return interfaceClass;
}
/**
* Common routine to do the JNDI lookup on the Home interface object
* username and password for jndi lookup are got from the configuration or from
* the messageContext if not found in the configuration
*/
private Object getEJBHome(SOAPService serviceHandler,
MessageContext msgContext,
String beanJndiName)
throws AxisFault
{
Object ejbHome = null;
// Set up an InitialContext and use it get the beanJndiName from JNDI
try {
Properties properties = null;
// collect all the properties we need to access JNDI:
// username, password, factoryclass, contextUrl
// username
String username = getStrOption(jndiUsername, serviceHandler);
if ((username == null) && (msgContext != null))
username = msgContext.getUsername();
if (username != null) {
if (properties == null)
properties = new Properties();
properties.setProperty(Context.SECURITY_PRINCIPAL, username);
}
// password
String password = getStrOption(jndiPassword, serviceHandler);
if ((password == null) && (msgContext != null))
password = msgContext.getPassword();
if (password != null) {
if (properties == null)
properties = new Properties();
properties.setProperty(Context.SECURITY_CREDENTIALS, password);
}
// factory class
String factoryClass = getStrOption(jndiContextClass, serviceHandler);
if (factoryClass != null) {
if (properties == null)
properties = new Properties();
properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, factoryClass);
}
// contextUrl
String contextUrl = getStrOption(jndiURL, serviceHandler);
if (contextUrl != null) {
if (properties == null)
properties = new Properties();
properties.setProperty(Context.PROVIDER_URL, contextUrl);
}
// get context using these properties
InitialContext context = getContext(properties);
// if we didn't get a context, fail
if (context == null)
throw new AxisFault( Messages.getMessage("cannotCreateInitialContext00"));
ejbHome = getEJBHome(context, beanJndiName);
if (ejbHome == null)
throw new AxisFault( Messages.getMessage("cannotFindJNDIHome00",beanJndiName));
}
// Should probably catch javax.naming.NameNotFoundException here
catch (Exception exception) {
entLog.info(Messages.getMessage("toAxisFault00"), exception);
throw AxisFault.makeFault(exception);
}
return ejbHome;
}
protected InitialContext getCachedContext()
throws javax.naming.NamingException
{
if (cached_context == null)
cached_context = new InitialContext();
return cached_context;
}
protected InitialContext getContext(Properties properties)
throws AxisFault, javax.naming.NamingException
{
// if we got any stuff from the configuration file
// create a new context using these properties
// otherwise, we get a default context and cache it for next time
return ((properties == null)
? getCachedContext()
: new InitialContext(properties));
}
protected Object getEJBHome(InitialContext context, String beanJndiName)
throws AxisFault, javax.naming.NamingException
{
// Do the JNDI lookup
return context.lookup(beanJndiName);
}
/**
* Override the default implementation such that we can include
* special handling for {@link java.rmi.ServerException}.
* <p/>
* Converts {@link java.rmi.ServerException} exceptions to
* {@link InvocationTargetException} exceptions with the same cause.
* This allows the axis framework to create a SOAP fault.
* </p>
*
* @see org.apache.axis.providers.java.RPCProvider#invokeMethod(org.apache.axis.MessageContext, java.lang.reflect.Method, java.lang.Object, java.lang.Object[])
*/
protected Object invokeMethod(MessageContext msgContext, Method method,
Object obj, Object[] argValues)
throws Exception {
try {
return super.invokeMethod(msgContext, method, obj, argValues);
} catch (InvocationTargetException ite) {
Throwable cause = getCause(ite);
if (cause instanceof java.rmi.ServerException) {
throw new InvocationTargetException(getCause(cause));
}
throw ite;
}
}
/**
* Get the cause of an exception, using reflection so that
* it still works under JDK 1.3
*
* @param original the original exception
* @return the cause of the exception, or the given exception if the cause cannot be discovered.
*/
private Throwable getCause(Throwable original) {
try {
Method method = original.getClass().getMethod("getCause", null);
Throwable cause = (Throwable) method.invoke(original, null);
if (cause != null) {
return cause;
}
} catch (NoSuchMethodException nsme) {
// ignore, this occurs under JDK 1.3
} catch (Throwable t) {
}
return original;
}
}
|