org.apache.axis.providers.java.JavaProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.axis.providers.java.JavaProvider.java

Source

/*
 * 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);
    }

}