/*
* 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 org.apache.axis.AxisEngine;
import org.apache.axis.AxisFault;
import org.apache.axis.Constants;
import org.apache.axis.Handler;
import org.apache.axis.Message;
import org.apache.axis.MessageContext;
import org.apache.axis.components.logger.LogFactory;
import org.apache.axis.description.JavaServiceDesc;
import org.apache.axis.description.OperationDesc;
import org.apache.axis.constants.Scope;
import org.apache.axis.handlers.soap.SOAPService;
import org.apache.axis.message.SOAPEnvelope;
import org.apache.axis.providers.BasicProvider;
import org.apache.axis.session.Session;
import org.apache.axis.utils.ClassUtils;
import org.apache.axis.utils.Messages;
import org.apache.axis.utils.XMLUtils;
import org.apache.axis.utils.cache.ClassCache;
import org.apache.axis.utils.cache.JavaClass;
import org.apache.commons.logging.Log;
import org.xml.sax.SAXException;
import javax.xml.rpc.holders.IntHolder;
import javax.xml.rpc.server.ServiceLifecycle;
import javax.xml.soap.SOAPMessage;
import javax.wsdl.OperationType;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.StringTokenizer;
/**
* Base class for Java dispatching. Fetches various fields out of envelope,
* looks up service object (possibly using session state), and delegates
* envelope body processing to subclass via abstract processMessage method.
*
* @author Doug Davis (dug@us.ibm.com)
* @author Carl Woolf (cwoolf@macromedia.com)
*/
public abstract class JavaProvider extends BasicProvider
{
protected static Log log =
LogFactory.getLog(JavaProvider.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_CLASSNAME = "className";
public static final String OPTION_ALLOWEDMETHODS = "allowedMethods";
public static final String OPTION_SCOPE = "scope";
/**
* Get the service object whose method actually provides the service.
* May look up in session table.
*/
public Object getServiceObject (MessageContext msgContext,
Handler service,
String clsName,
IntHolder scopeHolder)
throws Exception
{
String serviceName = msgContext.getService().getName();
// scope can be "Request", "Session", "Application", "Factory"
Scope scope = Scope.getScope((String)service.getOption(OPTION_SCOPE), Scope.DEFAULT);
scopeHolder.value = scope.getValue();
if (scope == Scope.REQUEST) {
// make a one-off
return getNewServiceObject(msgContext, clsName);
} else if (scope == Scope.SESSION) {
// What do we do if serviceName is null at this point???
if (serviceName == null)
serviceName = msgContext.getService().toString();
// look in incoming session
Session session = msgContext.getSession();
if (session != null) {
return getSessionServiceObject(session, serviceName,
msgContext, clsName);
} else {
// was no incoming session, sigh, treat as request scope
scopeHolder.value = Scope.DEFAULT.getValue();
return getNewServiceObject(msgContext, clsName);
}
} else if (scope == Scope.APPLICATION) {
// MUST be AxisEngine here!
return getApplicationScopedObject(msgContext, serviceName, clsName, scopeHolder);
} else if (scope == Scope.FACTORY) {
String objectID = msgContext.getStrProp("objectID");
if (objectID == null) {
return getApplicationScopedObject(msgContext, serviceName, clsName, scopeHolder);
}
SOAPService svc = (SOAPService)service;
Object ret = svc.serviceObjects.get(objectID);
if (ret == null) {
throw new AxisFault("NoSuchObject", null, null, null);
}
return ret;
}
// NOTREACHED
return null;
}
private Object getApplicationScopedObject(MessageContext msgContext, String serviceName, String clsName, IntHolder scopeHolder) throws Exception {
AxisEngine engine = msgContext.getAxisEngine();
Session appSession = engine.getApplicationSession();
if (appSession != null) {
return getSessionServiceObject(appSession, serviceName,
msgContext, clsName);
} else {
// was no application session - log an error and
// treat as request scope
log.error(Messages.getMessage("noAppSession"));
scopeHolder.value = Scope.DEFAULT.getValue();
return getNewServiceObject(msgContext, clsName);
}
}
/**
* Simple utility class for dealing with synchronization issues.
*/
class LockObject implements Serializable {
private boolean completed = false;
synchronized void waitUntilComplete() throws InterruptedException {
while (!completed) {
wait();
}
}
synchronized void complete() {
completed = true;
notifyAll();
}
}
/**
* Get a service object from a session. Handles threading / locking
* issues when multiple threads might be accessing the same session
* object, and ensures only one thread gets to create the service
* object if there isn't one already.
*/
private Object getSessionServiceObject(Session session,
String serviceName,
MessageContext msgContext,
String clsName) throws Exception {
Object obj = null;
boolean makeNewObject = false;
// This is a little tricky.
synchronized (session.getLockObject()) {
// store service objects in session, indexed by class name
obj = session.get(serviceName);
// If nothing there, put in a placeholder object so
// other threads wait for us to create the real
// service object.
if (obj == null) {
obj = new LockObject();
makeNewObject = true;
session.set(serviceName, obj);
msgContext.getService().addSession(session);
}
}
// OK, we DEFINITELY have something in obj at this point. Either
// it's the service object or it's a LockObject (ours or someone
// else's).
if (LockObject.class == obj.getClass()) {
LockObject lock = (LockObject)obj;
// If we were the lucky thread who got to install the
// placeholder, create a new service object and install it
// instead, then notify anyone waiting on the LockObject.
if (makeNewObject) {
try {
obj = getNewServiceObject(msgContext, clsName);
session.set(serviceName, obj);
msgContext.getService().addSession(session);
} catch(final Exception e) {
session.remove(serviceName);
throw e;
} finally {
lock.complete();
}
} else {
// It's someone else's LockObject, so wait around until
// it's completed.
lock.waitUntilComplete();
// Now we are guaranteed there is a service object in the
// session, so this next part doesn't need syncing
obj = session.get(serviceName);
}
}
return obj;
}
/**
* Return a new service object which, if it implements the ServiceLifecycle
* interface, has been init()ed.
*
* @param msgContext the MessageContext
* @param clsName the name of the class to instantiate
* @return an initialized service object
*/
private Object getNewServiceObject(MessageContext msgContext,
String clsName) throws Exception
{
Object serviceObject = makeNewServiceObject(msgContext, clsName);
if (serviceObject != null &&
serviceObject instanceof ServiceLifecycle) {
((ServiceLifecycle)serviceObject).init(
msgContext.getProperty(Constants.MC_SERVLET_ENDPOINT_CONTEXT));
}
return serviceObject;
}
/**
* Process the current message. Side-effect resEnv to create return value.
*
* @param msgContext self-explanatory
* @param reqEnv the request envelope
* @param resEnv the response envelope
* @param obj the service object itself
*/
public abstract void processMessage (MessageContext msgContext,
SOAPEnvelope reqEnv,
SOAPEnvelope resEnv,
Object obj)
throws Exception;
/**
* Invoke the message by obtaining various common fields, looking up
* the service object (via getServiceObject), and actually processing
* the message (via processMessage).
*/
public void invoke(MessageContext msgContext) throws AxisFault {
if (log.isDebugEnabled())
log.debug("Enter: JavaProvider::invoke (" + this + ")");
/* Find the service we're invoking so we can grab it's options */
/***************************************************************/
String serviceName = msgContext.getTargetService();
Handler service = msgContext.getService();
/* Now get the service (RPC) specific info */
/********************************************/
String clsName = getServiceClassName(service);
if ((clsName == null) || clsName.equals("")) {
throw new AxisFault("Server.NoClassForService",
Messages.getMessage("noOption00", getServiceClassNameOptionName(), serviceName),
null, null);
}
IntHolder scope = new IntHolder();
Object serviceObject = null;
try {
serviceObject = getServiceObject(msgContext, service, clsName, scope);
SOAPEnvelope resEnv = null;
// If there IS a response message AND this is a one-way operation,
// we delete the response message here. Note that this might
// cause confusing results in some cases - i.e. nothing fails,
// but your response headers don't go anywhere either. It might
// be nice if in the future there was a way to detect an error
// when trying to install a response message in a MessageContext
// associated with a one-way operation....
OperationDesc operation = msgContext.getOperation();
if (operation != null &&
OperationType.ONE_WAY.equals(operation.getMep())) {
msgContext.setResponseMessage(null);
} else {
Message resMsg = msgContext.getResponseMessage();
// If we didn't have a response message, make sure we set one up
// with the appropriate versions of SOAP and Schema
if (resMsg == null) {
resEnv = new SOAPEnvelope(msgContext.getSOAPConstants(),
msgContext.getSchemaVersion());
resMsg = new Message(resEnv);
String encoding = XMLUtils.getEncoding(msgContext);
resMsg.setProperty(SOAPMessage.CHARACTER_SET_ENCODING, encoding);
msgContext.setResponseMessage( resMsg );
} else {
resEnv = resMsg.getSOAPEnvelope();
}
}
Message reqMsg = msgContext.getRequestMessage();
SOAPEnvelope reqEnv = reqMsg.getSOAPEnvelope();
processMessage(msgContext, reqEnv, resEnv, serviceObject);
} catch( SAXException exp ) {
entLog.debug( Messages.getMessage("toAxisFault00"), exp);
Exception real = exp.getException();
if (real == null) {
real = exp;
}
throw AxisFault.makeFault(real);
} catch( Exception exp ) {
entLog.debug( Messages.getMessage("toAxisFault00"), exp);
AxisFault fault = AxisFault.makeFault(exp);
//make a note if this was a runtime fault, for better logging
if (exp instanceof RuntimeException) {
fault.addFaultDetail(Constants.QNAME_FAULTDETAIL_RUNTIMEEXCEPTION,
"true");
}
throw fault;
} finally {
// If this is a request scoped service object which implements
// ServiceLifecycle, let it know that it's being destroyed now.
if (serviceObject != null &&
scope.value == Scope.REQUEST.getValue() &&
serviceObject instanceof ServiceLifecycle)
{
((ServiceLifecycle)serviceObject).destroy();
}
}
if (log.isDebugEnabled())
log.debug("Exit: JavaProvider::invoke (" + this + ")");
}
private String getAllowedMethods(Handler service)
{
String val = (String)service.getOption(OPTION_ALLOWEDMETHODS);
if (val == null || val.length() == 0) {
// Try the old option for backwards-compatibility
val = (String)service.getOption("methodName");
}
return val;
}
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
/////// Default methods for java classes. Override, eg, for
/////// ejbeans
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
/**
* Default java service object comes from simply instantiating the
* class wrapped in jc
*
*/
protected Object makeNewServiceObject(MessageContext msgContext,
String clsName)
throws Exception
{
ClassLoader cl = msgContext.getClassLoader();
ClassCache cache = msgContext.getAxisEngine().getClassCache();
JavaClass jc = cache.lookup(clsName, cl);
return jc.getJavaClass().newInstance();
}
/**
* Return the class name of the service
*/
protected String getServiceClassName(Handler service)
{
return (String) service.getOption( getServiceClassNameOptionName() );
}
/**
* Return the option in the configuration that contains the service class
* name
*/
protected String getServiceClassNameOptionName()
{
return OPTION_CLASSNAME;
}
/**
* Returns the Class info about the service class.
*/
protected Class getServiceClass(String clsName,
SOAPService service,
MessageContext msgContext)
throws AxisFault {
ClassLoader cl = null;
Class serviceClass = null;
AxisEngine engine = service.getEngine();
// If we have a message context, use that to get classloader
// otherwise get the current threads classloader
if (msgContext != null) {
cl = msgContext.getClassLoader();
} else {
cl = Thread.currentThread().getContextClassLoader();
}
// If we have an engine, use its class cache
if (engine != null) {
ClassCache cache = engine.getClassCache();
try {
JavaClass jc = cache.lookup(clsName, cl);
serviceClass = jc.getJavaClass();
} catch (ClassNotFoundException e) {
log.error(Messages.getMessage("exception00"), e);
throw new AxisFault(Messages.getMessage("noClassForService00", clsName), e);
}
} else {
// if no engine, we don't have a cache, use Class.forName instead.
try {
serviceClass = ClassUtils.forName(clsName, true, cl);
} catch (ClassNotFoundException e) {
log.error(Messages.getMessage("exception00"), e);
throw new AxisFault(Messages.getMessage("noClassForService00", clsName), e);
}
}
return serviceClass;
}
/**
* Fill in a service description with the correct impl class
* and typemapping set. This uses methods that can be overridden by
* other providers (like the EJBProvider) to get the class from the
* right place.
*/
public void initServiceDesc(SOAPService service, MessageContext msgContext)
throws AxisFault
{
// Set up the Implementation class for the service
String clsName = getServiceClassName(service);
if (clsName == null) {
throw new AxisFault(Messages.getMessage("noServiceClass"));
}
Class cls = getServiceClass(clsName, service, msgContext);
JavaServiceDesc serviceDescription = (JavaServiceDesc)service.getServiceDescription();
// And the allowed methods, if necessary
if (serviceDescription.getAllowedMethods() == null && service != null) {
String allowedMethods = getAllowedMethods(service);
if (allowedMethods != null && !"*".equals(allowedMethods)) {
ArrayList methodList = new ArrayList();
StringTokenizer tokenizer = new StringTokenizer(allowedMethods, " ,");
while (tokenizer.hasMoreTokens()) {
methodList.add(tokenizer.nextToken());
}
serviceDescription.setAllowedMethods(methodList);
}
}
serviceDescription.loadServiceDescByIntrospection(cls);
}
}
|