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