| /* |
| * Copyright 2002-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.description; |
| |
| import org.apache.axis.AxisServiceConfig; |
| import org.apache.axis.Constants; |
| import org.apache.axis.InternalException; |
| import org.apache.axis.AxisProperties; |
| import org.apache.axis.components.logger.LogFactory; |
| import org.apache.axis.encoding.*; |
| import org.apache.axis.constants.Style; |
| import org.apache.axis.constants.Use; |
| import org.apache.axis.message.SOAPBodyElement; |
| import org.apache.axis.message.SOAPEnvelope; |
| import org.apache.axis.utils.JavaUtils; |
| import org.apache.axis.utils.Messages; |
| import org.apache.axis.utils.bytecode.ParamNameExtractor; |
| import org.apache.axis.wsdl.Skeleton; |
| import org.apache.axis.wsdl.fromJava.Namespaces; |
| import org.apache.commons.logging.Log; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| |
| import javax.xml.namespace.QName; |
| import javax.xml.rpc.holders.Holder; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.StringTokenizer; |
| |
| |
| /** |
| * A ServiceDesc is an abstract description of a service. |
| * |
| * ServiceDescs contain OperationDescs, which are descriptions of operations. |
| * The information about a service's operations comes from one of two places: |
| * 1) deployment, or 2) introspection. |
| * |
| * @author Glen Daniels (gdaniels@apache.org) |
| */ |
| public class JavaServiceDesc implements ServiceDesc { |
| protected static Log log = |
| LogFactory.getLog(JavaServiceDesc.class.getName()); |
| |
| /** The name of this service */ |
| private String name = null; |
| |
| /** The documentation of this service */ |
| private String documentation = null; |
| |
| /** Style/Use */ |
| private Style style = Style.RPC; |
| private Use use = Use.ENCODED; |
| |
| // Style and Use are related. By default, if Style==RPC, Use should be |
| // ENCODED. But if Style==DOCUMENT, Use should be LITERAL. So we want |
| // to keep the defaults synced until someone explicitly sets the Use. |
| private boolean useSet = false; |
| |
| /** Our operations - a list of OperationDescs */ |
| private ArrayList operations = new ArrayList(); |
| |
| /** A collection of namespaces which will map to this service */ |
| private List namespaceMappings = null; |
| |
| /** |
| * Where does our WSDL document live? If this is non-null, the "?WSDL" |
| * generation will automatically return this file instead of dynamically |
| * creating a WSDL. BE CAREFUL because this means that Handlers will |
| * not be able to add to the WSDL for extensions/headers.... |
| */ |
| private String wsdlFileName = null; |
| |
| /** |
| * An endpoint URL which someone has specified for this service. If |
| * this is set, WSDL generation will pick it up instead of defaulting |
| * to the transport URL. |
| */ |
| private String endpointURL = null; |
| |
| /** Place to store user-extensible service-related properties */ |
| private HashMap properties = null; |
| |
| /** Lookup caches */ |
| private HashMap name2OperationsMap = null; |
| private HashMap qname2OperationsMap = null; |
| private transient HashMap method2OperationMap = new HashMap(); |
| |
| // THE FOLLOWING STUFF IS ALL JAVA-SPECIFIC, AND WILL BE FACTORED INTO |
| // A JAVA-SPECIFIC SUBCLASS. --Glen |
| |
| /** List of allowed methods */ |
| /** null allows everything, an empty ArrayList allows nothing */ |
| private List allowedMethods = null; |
| |
| /** List if disallowed methods */ |
| private List disallowedMethods = null; |
| |
| /** Implementation class */ |
| private Class implClass = null; |
| |
| /** |
| * Is the implementation a Skeleton? If this is true, it will generate |
| * a Fault to provide OperationDescs via WSDD. |
| */ |
| private boolean isSkeletonClass = false; |
| |
| /** Cached copy of the skeleton "getOperationDescByName" method */ |
| private transient Method skelMethod = null; |
| |
| /** Classes at which we should stop looking up the inheritance chain |
| * when introspecting |
| */ |
| private ArrayList stopClasses = null; |
| |
| /** Lookup caches */ |
| private transient HashMap method2ParamsMap = new HashMap(); |
| private OperationDesc messageServiceDefaultOp = null; |
| |
| /** Method names for which we have completed any introspection necessary */ |
| private ArrayList completedNames = new ArrayList(); |
| |
| /** Our typemapping for resolving Java<->XML type issues */ |
| private TypeMapping tm = null; |
| private TypeMappingRegistry tmr = null; |
| |
| private boolean haveAllSkeletonMethods = false; |
| private boolean introspectionComplete = false; |
| |
| /** |
| * Default constructor |
| */ |
| public JavaServiceDesc() { |
| } |
| |
| /** |
| * What kind of service is this? |
| * @return |
| */ |
| public Style getStyle() { |
| return style; |
| } |
| |
| public void setStyle(Style style) { |
| this.style = style; |
| if (!useSet) { |
| // Use hasn't been explicitly set, so track style |
| use = style == Style.RPC ? Use.ENCODED : Use.LITERAL; |
| } |
| } |
| |
| |
| /** |
| * What kind of use is this? |
| * @return |
| */ |
| public Use getUse() { |
| return use; |
| } |
| |
| public void setUse(Use use) { |
| useSet = true; |
| this.use = use; |
| } |
| |
| /** |
| * Determine whether or not this is a "wrapped" invocation, i.e. whether |
| * the outermost XML element of the "main" body element represents a |
| * method call, with the immediate children of that element representing |
| * arguments to the method. |
| * |
| * @return true if this is wrapped (i.e. RPC or WRAPPED style), |
| * false otherwise |
| */ |
| public boolean isWrapped() |
| { |
| return ((style == Style.RPC) || |
| (style == Style.WRAPPED)); |
| } |
| |
| /** |
| * the wsdl file of the service. |
| * When null, it means that the wsdl should be autogenerated |
| * @return filename or null |
| */ |
| public String getWSDLFile() { |
| return wsdlFileName; |
| } |
| |
| /** |
| * set the wsdl file of the service; this causes the named |
| * file to be returned on a ?wsdl, probe, not introspection |
| * generated wsdl. |
| * @param wsdlFileName filename or null to re-enable introspection |
| */ |
| public void setWSDLFile(String wsdlFileName) { |
| this.wsdlFileName = wsdlFileName; |
| } |
| |
| public List getAllowedMethods() { |
| return allowedMethods; |
| } |
| |
| public void setAllowedMethods(List allowedMethods) { |
| this.allowedMethods = allowedMethods; |
| } |
| |
| public Class getImplClass() { |
| return implClass; |
| } |
| |
| /** |
| * set the implementation class |
| * <p> |
| * Warning: You cannot call getInitializedServiceDesc() after setting this |
| * as it uses this to indicate its work has already been done. |
| * |
| * @param implClass |
| * @throws IllegalArgumentException if the implementation class is already |
| * set |
| */ |
| public void setImplClass(Class implClass) { |
| if (this.implClass != null) |
| throw new IllegalArgumentException( |
| Messages.getMessage("implAlreadySet")); |
| |
| this.implClass = implClass; |
| if (Skeleton.class.isAssignableFrom(implClass)) { |
| isSkeletonClass = true; |
| loadSkeletonOperations(); |
| } |
| } |
| |
| private void loadSkeletonOperations() { |
| Method method = null; |
| try { |
| method = implClass.getDeclaredMethod("getOperationDescs", |
| new Class [] {}); |
| } catch (NoSuchMethodException e) { |
| } catch (SecurityException e) { |
| } |
| if (method == null) { |
| // FIXME : Throw an error? |
| return; |
| } |
| |
| try { |
| Collection opers = (Collection)method.invoke(implClass, null); |
| for (Iterator i = opers.iterator(); i.hasNext();) { |
| OperationDesc skelDesc = (OperationDesc)i.next(); |
| addOperationDesc(skelDesc); |
| } |
| } catch (IllegalAccessException e) { |
| if(log.isDebugEnabled()) { |
| log.debug(Messages.getMessage("exception00"), e); |
| } |
| return; |
| } catch (IllegalArgumentException e) { |
| if(log.isDebugEnabled()) { |
| log.debug(Messages.getMessage("exception00"), e); |
| } |
| return; |
| } catch (InvocationTargetException e) { |
| if(log.isDebugEnabled()) { |
| log.debug(Messages.getMessage("exception00"), e); |
| } |
| return; |
| } |
| haveAllSkeletonMethods = true; |
| } |
| |
| public TypeMapping getTypeMapping() { |
| if(tm == null) { |
| return DefaultTypeMappingImpl.getSingletonDelegate(); |
| // throw new RuntimeException(Messages.getMessage("noDefaultTypeMapping00")); |
| } |
| return tm; |
| } |
| |
| public void setTypeMapping(TypeMapping tm) { |
| this.tm = tm; |
| } |
| |
| /** |
| * the name of the service |
| */ |
| public String getName() { |
| return name; |
| } |
| |
| /** |
| * the name of the service |
| * @param name |
| */ |
| public void setName(String name) { |
| this.name = name; |
| } |
| |
| /** |
| * get the documentation for the service |
| */ |
| public String getDocumentation() { |
| return documentation; |
| } |
| |
| /** |
| * set the documentation for the service |
| */ |
| public void setDocumentation(String documentation) { |
| this.documentation = documentation; |
| } |
| |
| public ArrayList getStopClasses() { |
| return stopClasses; |
| } |
| |
| public void setStopClasses(ArrayList stopClasses) { |
| this.stopClasses = stopClasses; |
| } |
| |
| public List getDisallowedMethods() { |
| return disallowedMethods; |
| } |
| |
| public void setDisallowedMethods(List disallowedMethods) { |
| this.disallowedMethods = disallowedMethods; |
| } |
| |
| public void removeOperationDesc(OperationDesc operation) { |
| operations.remove(operation); |
| operation.setParent(null); |
| |
| if (name2OperationsMap != null) { |
| String name = operation.getName(); |
| ArrayList overloads = (ArrayList)name2OperationsMap.get(name); |
| if (overloads != null) { |
| overloads.remove(operation); |
| if (overloads.size() == 0) { |
| name2OperationsMap.remove(name); |
| } |
| } |
| } |
| |
| if (qname2OperationsMap != null) { |
| QName qname = operation.getElementQName(); |
| ArrayList list = (ArrayList)qname2OperationsMap.get(qname); |
| if (list != null) { |
| list.remove(operation); |
| } |
| } |
| |
| if (method2OperationMap != null) { |
| Method method = operation.getMethod(); |
| if (method != null) { |
| method2OperationMap.remove(method); |
| } |
| } |
| } |
| |
| public void addOperationDesc(OperationDesc operation) |
| { |
| operations.add(operation); |
| operation.setParent(this); |
| if (name2OperationsMap == null) { |
| name2OperationsMap = new HashMap(); |
| } |
| |
| // Add name to name2Operations Map |
| String name = operation.getName(); |
| ArrayList overloads = (ArrayList)name2OperationsMap.get(name); |
| if (overloads == null) { |
| overloads = new ArrayList(); |
| name2OperationsMap.put(name, overloads); |
| } else if (JavaUtils.isTrue( |
| AxisProperties.getProperty(Constants.WSIBP11_COMPAT_PROPERTY)) && |
| overloads.size() > 0) { |
| throw new RuntimeException(Messages.getMessage("noOverloadedOperations", name)); |
| } |
| overloads.add(operation); |
| } |
| |
| /** |
| * get all the operations as a list of OperationDescs. |
| * this method triggers an evaluation of the valid operations by |
| * introspection, so use sparingly |
| * @return reference to the operations array. This is not a copy |
| */ |
| public ArrayList getOperations() |
| { |
| loadServiceDescByIntrospection(); // Just in case... |
| return operations; |
| } |
| |
| /** |
| * get all overloaded operations by name |
| * @param methodName |
| * @return null for no match, or an array of OperationDesc objects |
| */ |
| public OperationDesc [] getOperationsByName(String methodName) |
| { |
| getSyncedOperationsForName(implClass, methodName); |
| |
| if (name2OperationsMap == null) |
| return null; |
| |
| ArrayList overloads = (ArrayList)name2OperationsMap.get(methodName); |
| if (overloads == null) { |
| return null; |
| } |
| |
| OperationDesc [] array = new OperationDesc [overloads.size()]; |
| return (OperationDesc[])overloads.toArray(array); |
| } |
| |
| /** |
| * Return an operation matching the given method name. Note that if we |
| * have multiple overloads for this method, we will return the first one. |
| * @return null for no match |
| */ |
| public OperationDesc getOperationByName(String methodName) |
| { |
| // If we need to load up operations from introspection data, do it. |
| // This returns fast if we don't need to do anything, so it's not very |
| // expensive. |
| getSyncedOperationsForName(implClass, methodName); |
| |
| if (name2OperationsMap == null) |
| return null; |
| |
| ArrayList overloads = (ArrayList)name2OperationsMap.get(methodName); |
| if (overloads == null) { |
| return null; |
| } |
| |
| return (OperationDesc)overloads.get(0); |
| } |
| |
| /** |
| * Map an XML QName to an operation. Returns the first one it finds |
| * in the case of mulitple matches. |
| * @return null for no match |
| */ |
| public OperationDesc getOperationByElementQName(QName qname) |
| { |
| OperationDesc [] overloads = getOperationsByQName(qname); |
| |
| // Return the first one.... |
| if ((overloads != null) && overloads.length > 0) |
| return overloads[0]; |
| |
| return null; |
| } |
| |
| /** |
| * Return all operations which match this QName (i.e. get all the |
| * overloads) |
| * @return null for no match |
| */ |
| public OperationDesc [] getOperationsByQName(QName qname) |
| { |
| // Look in our mapping of QNames -> operations. |
| |
| // But first, let's make sure we've initialized said mapping.... |
| initQNameMap(); |
| |
| ArrayList overloads = (ArrayList)qname2OperationsMap.get(qname); |
| if (overloads == null) { |
| // Nothing specifically matching this QName. |
| if (name2OperationsMap != null) { |
| if ((isWrapped() || |
| ((style == Style.MESSAGE) && |
| (getDefaultNamespace() == null)))) { |
| // Try ignoring the namespace....? |
| overloads = (ArrayList) name2OperationsMap.get(qname.getLocalPart()); |
| } else { |
| // TODO the above code is weird: a JavaServiceDesc can be document or rpc and |
| // still define a WSDL operation using a wrapper style mapping. |
| // The following code handles this case. |
| Object ops = name2OperationsMap.get(qname.getLocalPart()); |
| if (ops != null) { |
| overloads = new ArrayList((Collection) ops); |
| for (Iterator iter = overloads.iterator(); iter.hasNext();) { |
| OperationDesc operationDesc = (OperationDesc) iter.next(); |
| if (Style.WRAPPED != operationDesc.getStyle()) { |
| iter.remove(); |
| } |
| } |
| } |
| } |
| } |
| // Handle the case where a single Message-style operation wants |
| // to accept anything. |
| if ((style == Style.MESSAGE) && (messageServiceDefaultOp != null)) |
| return new OperationDesc [] { messageServiceDefaultOp }; |
| |
| if (overloads == null) |
| return null; |
| } |
| |
| getSyncedOperationsForName(implClass, |
| ((OperationDesc)overloads.get(0)).getName()); |
| |
| // Convert to array before sorting to avoid concurrency issues |
| OperationDesc[] array = (OperationDesc[])overloads.toArray( |
| new OperationDesc[overloads.size()]); |
| |
| // Sort the overloads by number of arguments - prevents us calling methods |
| // with more parameters than supplied in the request (with missing parameters |
| // defaulted to null) when a perfectly good method exists with exactly the |
| // supplied parameters. |
| Arrays.sort(array, |
| new Comparator() { |
| public int compare(Object o1, Object o2) |
| { |
| Method meth1 = ((OperationDesc)o1).getMethod(); |
| Method meth2 = ((OperationDesc)o2).getMethod(); |
| return (meth1.getParameterTypes().length - |
| meth2.getParameterTypes().length); |
| } |
| }); |
| |
| return array; |
| } |
| |
| private synchronized void initQNameMap() { |
| if (qname2OperationsMap == null) { |
| loadServiceDescByIntrospection(); |
| |
| qname2OperationsMap = new HashMap(); |
| for (Iterator i = operations.iterator(); i.hasNext();) { |
| OperationDesc operationDesc = (OperationDesc) i.next(); |
| QName qname = operationDesc.getElementQName(); |
| ArrayList list = (ArrayList)qname2OperationsMap.get(qname); |
| if (list == null) { |
| list = new ArrayList(); |
| qname2OperationsMap.put(qname, list); |
| } |
| list.add(operationDesc); |
| } |
| } |
| } |
| |
| /** |
| * Synchronize an existing OperationDesc to a java.lang.Method. |
| * |
| * This method is used when the deployer has specified operation metadata |
| * and we want to match that up with a real java Method so that the |
| * Operation-level dispatch carries us all the way to the implementation. |
| * Search the declared methods on the implementation class to find one |
| * with an argument list which matches our parameter list. |
| */ |
| private void syncOperationToClass(OperationDesc oper, Class implClass) |
| { |
| // ------------------------------------------------ |
| // Developer Note: |
| // |
| // The goal of the sync code is to associate |
| // the OperationDesc/ParamterDesc with the |
| // target Method. There are a number of ways to get to this |
| // point depending on what information |
| // is available. Here are the main scenarios: |
| // |
| // A) Deployment with wsdd (non-skeleton): |
| // * OperationDesc/ParameterDesc loaded from deploy.wsdd |
| // * Loaded ParameterDesc does not have javaType, |
| // so it is discovered using the TypeMappingRegistry |
| // (also loaded via deploy.wsdd) and the |
| // typeQName specified by the ParameterDesc. |
| // * Sync occurs using the discovered |
| // javaTypes and the javaTypes of the Method |
| // parameters |
| // |
| // B) Deployment with no wsdd OperationDesc info (non-skeleton): |
| // * Implementation Class introspected to build |
| // OperationDesc/ParameterDesc. |
| // * ParameterDesc is known via introspection. |
| // * ParameterDesc are discovered using javaType |
| // and TypeMappingRegistry. |
| // * Sync occurs using the introspected |
| // javaTypes and the javaTypes of the Method |
| // parameters |
| // |
| // C) Deployment with wsdd (skeleton): |
| // * OperationDesc/ParameterDesc loaded from the Skeleton |
| // * In this scenario the ParameterDescs' already |
| // have javaTypes (see E below). |
| // * Sync occurs using the ParameterDesc |
| // javaTypes and the javaTypes of the Method |
| // parameters. |
| // |
| // D) Commandline Java2WSDL loading non-Skeleton Class/Interface |
| // * Class/Interface introspected to build |
| // OperationDesc/ParameterDesc. |
| // * The javaTypes of the ParameterDesc are set using introspection. |
| // * typeQNames are determined for built-in types using |
| // from the default TypeMappingRegistry. Other |
| // typeQNames are guessed from the javaType. Note |
| // that there is no loaded TypeMappingRegistry. |
| // * Sync occurs using the ParameterDesc |
| // javaTypes and the javaTypes of the Method |
| // parameters. |
| // |
| // E) Commandline Java2WSDL loading Skeleton Class |
| // * OperationDesc/ParameterDesc loaded from Skeleton |
| // * Each ParameterDesc has an appropriate typeQName |
| // * Each ParameterDesc also has a javaType, which is |
| // essential for sync'ing up with the |
| // method since there is no loaded TypeMappingRegistry. |
| // * Syncronization occurs using the ParameterDesc |
| // javaTypes and the javaTypes of the Method |
| // parameters. |
| // |
| // So in each scenario, the ultimate sync'ing occurs |
| // using the javaTypes of the ParameterDescs and the |
| // javaTypes of the Method parameters. |
| // |
| // ------------------------------------------------ |
| |
| // If we're already mapped to a Java method, no need to do anything. |
| if (oper.getMethod() != null) |
| return; |
| |
| // Find the method. We do this once for each Operation. |
| |
| Method[] methods = getMethods(implClass); |
| // A place to keep track of possible matches |
| Method possibleMatch = null; |
| |
| for (int i = 0; i < methods.length; i++) { |
| Method method = methods[i]; |
| if (Modifier.isPublic(method.getModifiers()) && |
| method.getName().equals(oper.getName()) && |
| method2OperationMap.get(method) == null) { |
| |
| if (style == Style.MESSAGE) { |
| int messageOperType = checkMessageMethod(method); |
| if(messageOperType == OperationDesc.MSG_METHOD_NONCONFORMING) continue; |
| if (messageOperType == -1) { |
| throw new InternalException("Couldn't match method to any of the allowable message-style patterns!"); |
| } |
| oper.setMessageOperationStyle(messageOperType); |
| |
| // Don't bother checking params if we're message style |
| possibleMatch = method; |
| break; |
| } |
| |
| // Check params |
| Class [] paramTypes = method.getParameterTypes(); |
| if (paramTypes.length != oper.getNumParams()) |
| continue; |
| |
| int j; |
| boolean conversionNecessary = false; |
| for (j = 0; j < paramTypes.length; j++) { |
| Class type = paramTypes[j]; |
| Class actualType = type; |
| if (Holder.class.isAssignableFrom(type)) { |
| actualType = JavaUtils.getHolderValueType(type); |
| } |
| ParameterDesc param = oper.getParameter(j); |
| QName typeQName = param.getTypeQName(); |
| if (typeQName == null) { |
| // No typeQName is available. Set it using |
| // information from the actual type. |
| // (Scenarios B and D) |
| // There is no need to try and match with |
| // the Method parameter javaType because |
| // the ParameterDesc is being constructed |
| // by introspecting the Method. |
| typeQName = getTypeMapping().getTypeQName(actualType); |
| param.setTypeQName(typeQName); |
| } else { |
| // A type qname is available. |
| // Ensure that the ParameterDesc javaType |
| // is convertable to the Method parameter type |
| // |
| // Use the available javaType (Scenarios C and E) |
| // or get one from the TMR (Scenario A). |
| Class paramClass = param.getJavaType(); |
| if (paramClass != null && |
| JavaUtils.getHolderValueType(paramClass) != null) { |
| paramClass = JavaUtils.getHolderValueType(paramClass); |
| } |
| if (paramClass == null) { |
| paramClass = getTypeMapping().getClassForQName(param.getTypeQName(), |
| type); |
| } |
| |
| if (paramClass != null) { |
| // This is a match if the paramClass is somehow |
| // convertable to the "real" parameter type. If not, |
| // break out of this loop. |
| if (!JavaUtils.isConvertable(paramClass, actualType)) { |
| break; |
| } |
| |
| if (!actualType.isAssignableFrom(paramClass)) { |
| // This doesn't fit without conversion |
| conversionNecessary = true; |
| } |
| } |
| } |
| // In all scenarios the ParameterDesc javaType is set to |
| // match the javaType in the corresponding parameter. |
| // This is essential. |
| param.setJavaType(type); |
| } |
| |
| if (j != paramTypes.length) { |
| // failed. |
| continue; |
| } |
| |
| // This is our latest possibility |
| possibleMatch = method; |
| |
| // If this is exactly it, stop now. Otherwise keep looking |
| // just in case we find a better match. |
| if (!conversionNecessary) { |
| break; |
| } |
| |
| } |
| } |
| |
| // At this point, we may or may not have a possible match. |
| // FIXME : Should we prefer an exact match from a base class over |
| // a with-conversion match from the target class? If so, |
| // we'll need to change the logic below. |
| if (possibleMatch != null) { |
| Class returnClass = possibleMatch.getReturnType(); |
| oper.setReturnClass(returnClass); |
| |
| QName returnType = oper.getReturnType(); |
| if (returnType == null) { |
| oper.setReturnType(getTypeMapping().getTypeQName(returnClass)); |
| } |
| |
| // Do the faults |
| createFaultMetadata(possibleMatch, oper); |
| |
| oper.setMethod(possibleMatch); |
| method2OperationMap.put(possibleMatch, oper); |
| return; |
| } |
| |
| // Didn't find a match. Try the superclass, if appropriate |
| Class superClass = implClass.getSuperclass(); |
| if (superClass != null && |
| !superClass.getName().startsWith("java.") && |
| !superClass.getName().startsWith("javax.") && |
| (stopClasses == null || |
| !stopClasses.contains(superClass.getName()))) { |
| syncOperationToClass(oper, superClass); |
| } |
| |
| // Exception if sync fails to find method for operation |
| if (oper.getMethod() == null) { |
| InternalException ie = |
| new InternalException(Messages.getMessage("serviceDescOperSync00", |
| oper.getName(), |
| implClass.getName())); |
| throw ie; |
| } |
| } |
| |
| private Method[] getMethods(Class implClass) { |
| if (implClass.isInterface()){ |
| // only return methods that are not part of start classes |
| List methodsList = new ArrayList(); |
| Method[] methods = implClass.getMethods(); |
| if (methods != null) { |
| for (int i = 0; i < methods.length; i++) { |
| String declaringClass = methods[i].getDeclaringClass().getName(); |
| if (!declaringClass.startsWith("java.") && |
| !declaringClass.startsWith("javax.")) { |
| methodsList.add(methods[i]); |
| } |
| } |
| } |
| return (Method[])methodsList.toArray(new Method[]{}); |
| } else { |
| return implClass.getDeclaredMethods(); |
| } |
| } |
| |
| private int checkMessageMethod(Method method) { |
| // Collect the types so we know what we're dealing with in the target |
| // method. |
| Class [] params = method.getParameterTypes(); |
| |
| if (params.length == 1) { |
| if ((params[0] == Element[].class) && |
| (method.getReturnType() == Element[].class)) { |
| return OperationDesc.MSG_METHOD_ELEMENTARRAY; |
| } |
| |
| if ((params[0] == SOAPBodyElement[].class) && |
| (method.getReturnType() == SOAPBodyElement[].class)) { |
| return OperationDesc.MSG_METHOD_BODYARRAY; |
| } |
| |
| if ((params[0] == Document.class) && |
| (method.getReturnType() == Document.class)) { |
| return OperationDesc.MSG_METHOD_DOCUMENT; |
| } |
| } else if (params.length == 2) { |
| if (((params[0] == SOAPEnvelope.class) && |
| (params[1] == SOAPEnvelope.class)) || |
| ((params[0] == javax.xml.soap.SOAPEnvelope.class) && |
| (params[1] == javax.xml.soap.SOAPEnvelope.class)) && |
| (method.getReturnType() == void.class)){ |
| return OperationDesc.MSG_METHOD_SOAPENVELOPE; |
| } |
| } |
| if( null != allowedMethods && !allowedMethods.isEmpty() ) |
| throw new InternalException (Messages.getMessage("badMsgMethodParams", |
| method.getName())); |
| return OperationDesc.MSG_METHOD_NONCONFORMING; |
| } |
| |
| /** |
| * Fill in a service description by introspecting the implementation |
| * class. |
| */ |
| public void loadServiceDescByIntrospection() |
| { |
| loadServiceDescByIntrospection(implClass); |
| |
| // Setting this to null means there is nothing more to do, and it |
| // avoids future string compares. |
| completedNames = null; |
| } |
| |
| /** |
| * Fill in a service description by introspecting the implementation |
| * class. |
| */ |
| public void loadServiceDescByIntrospection(Class implClass) { |
| if (introspectionComplete || implClass == null) { |
| return; |
| } |
| |
| // set the implementation class for the service description |
| this.implClass = implClass; |
| if (Skeleton.class.isAssignableFrom(implClass)) { |
| isSkeletonClass = true; |
| loadSkeletonOperations(); |
| } |
| |
| /** If the class knows what it should be exporting, |
| * respect its wishes. |
| */ |
| AxisServiceConfig axisConfig = null; |
| try { |
| Method method = implClass.getDeclaredMethod( |
| "getAxisServiceConfig", new Class [] {}); |
| if (method != null && Modifier.isStatic(method.getModifiers())) { |
| axisConfig = (AxisServiceConfig)method.invoke(null, null); |
| } |
| } catch (Exception e) { |
| // No problem, just continue without... |
| } |
| |
| if (axisConfig != null) { |
| String allowedMethodsStr = axisConfig.getAllowedMethods(); |
| if (allowedMethodsStr != null && !"*".equals(allowedMethodsStr)) { |
| ArrayList methodList = new ArrayList(); |
| StringTokenizer tokenizer = |
| new StringTokenizer(allowedMethodsStr, " ,"); |
| while (tokenizer.hasMoreTokens()) { |
| methodList.add(tokenizer.nextToken()); |
| } |
| setAllowedMethods(methodList); |
| } |
| } |
| |
| loadServiceDescByIntrospectionRecursive(implClass); |
| |
| // All operations should now be synchronized. Check it. |
| for (Iterator iterator = operations.iterator(); iterator.hasNext();) { |
| OperationDesc operation = (OperationDesc) iterator.next(); |
| if (operation.getMethod() == null) { |
| throw new InternalException( |
| Messages.getMessage("badWSDDOperation", |
| operation.getName(), |
| "" + operation.getNumParams())); |
| } |
| } |
| |
| if ((style == Style.MESSAGE) && operations.size() == 1) { |
| messageServiceDefaultOp = (OperationDesc)operations.get(0); |
| } |
| |
| introspectionComplete = true; |
| } |
| |
| /** |
| * Is this method from ServiceLifeCycle interface? |
| * @param m |
| * @return true if this method is from ServiceLifeCycle interface |
| */ |
| private boolean isServiceLifeCycleMethod(Class implClass, Method m) { |
| if(javax.xml.rpc.server.ServiceLifecycle.class.isAssignableFrom(implClass)) { |
| String methodName = m.getName(); |
| |
| if(methodName.equals("init")) { |
| // Check if the method signature is |
| // "public abstract void init(Object context) throws ServiceException;" |
| Class[] classes = m.getParameterTypes(); |
| if(classes != null && |
| classes.length == 1 && |
| classes[0] == Object.class && |
| m.getReturnType() == Void.TYPE) { |
| return true; |
| } |
| } else if (methodName.equals("destroy")){ |
| // Check if the method signature is |
| // "public abstract void destroy();" |
| Class[] classes = m.getParameterTypes(); |
| if(classes != null && |
| classes.length == 0 && |
| m.getReturnType() == Void.TYPE) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Recursive helper class for loadServiceDescByIntrospection |
| */ |
| private void loadServiceDescByIntrospectionRecursive(Class implClass) |
| { |
| if (Skeleton.class.equals(implClass)) { |
| return; |
| } |
| |
| Method [] methods = getMethods(implClass); |
| |
| for (int i = 0; i < methods.length; i++) { |
| if (Modifier.isPublic(methods[i].getModifiers()) && !isServiceLifeCycleMethod(implClass, methods[i])) { |
| getSyncedOperationsForName(implClass, methods[i].getName()); |
| } |
| } |
| |
| if (implClass.isInterface()) { |
| Class [] superClasses = implClass.getInterfaces(); |
| for (int i = 0; i < superClasses.length; i++) { |
| Class superClass = superClasses[i]; |
| if (!superClass.getName().startsWith("java.") && |
| !superClass.getName().startsWith("javax.") && |
| (stopClasses == null || |
| !stopClasses.contains(superClass.getName()))) { |
| loadServiceDescByIntrospectionRecursive(superClass); |
| } |
| } |
| } else { |
| Class superClass = implClass.getSuperclass(); |
| if (superClass != null && |
| !superClass.getName().startsWith("java.") && |
| !superClass.getName().startsWith("javax.") && |
| (stopClasses == null || |
| !stopClasses.contains(superClass.getName()))) { |
| loadServiceDescByIntrospectionRecursive(superClass); |
| } |
| } |
| } |
| |
| /** |
| * Fill in a service description by introspecting the implementation |
| * class. This version takes the implementation class and the in-scope |
| * TypeMapping. |
| */ |
| public void loadServiceDescByIntrospection(Class cls, TypeMapping tm) |
| { |
| // Should we complain if the implClass changes??? |
| implClass = cls; |
| this.tm = tm; |
| |
| if (Skeleton.class.isAssignableFrom(implClass)) { |
| isSkeletonClass = true; |
| loadSkeletonOperations(); |
| } |
| |
| loadServiceDescByIntrospection(); |
| } |
| |
| /** |
| * Makes sure we have completely synchronized OperationDescs with |
| * the implementation class. |
| */ |
| private void getSyncedOperationsForName(Class implClass, String methodName) |
| { |
| // If we're a Skeleton deployment, skip the statics. |
| if (isSkeletonClass) { |
| if (methodName.equals("getOperationDescByName") || |
| methodName.equals("getOperationDescs")) |
| return; |
| } |
| |
| // If we have no implementation class, don't worry about it (we're |
| // probably on the client) |
| if (implClass == null) |
| return; |
| |
| // If we're done introspecting, or have completed this method, return |
| if (completedNames == null || completedNames.contains(methodName)) |
| return; |
| |
| // Skip it if it's not a sanctioned method name |
| if ((allowedMethods != null) && |
| !allowedMethods.contains(methodName)) |
| return; |
| |
| if ((disallowedMethods != null) && |
| disallowedMethods.contains(methodName)) |
| return; |
| |
| // If we're a skeleton class, make sure we don't already have any |
| // OperationDescs for this name (as that might cause conflicts), |
| // then load them up from the Skeleton class. |
| if (isSkeletonClass && !haveAllSkeletonMethods) { |
| // FIXME : Check for existing ones and fault if found |
| |
| if (skelMethod == null) { |
| // Grab metadata from the Skeleton for parameter info |
| try { |
| skelMethod = implClass.getDeclaredMethod( |
| "getOperationDescByName", |
| new Class [] { String.class }); |
| } catch (NoSuchMethodException e) { |
| } catch (SecurityException e) { |
| } |
| if (skelMethod == null) { |
| // FIXME : Throw an error? |
| return; |
| } |
| } |
| try { |
| List skelList = |
| (List)skelMethod.invoke(implClass, |
| new Object [] { methodName }); |
| if (skelList != null) { |
| Iterator i = skelList.iterator(); |
| while (i.hasNext()) { |
| addOperationDesc((OperationDesc)i.next()); |
| } |
| } |
| } catch (IllegalAccessException e) { |
| if(log.isDebugEnabled()) { |
| log.debug(Messages.getMessage("exception00"), e); |
| } |
| return; |
| } catch (IllegalArgumentException e) { |
| if(log.isDebugEnabled()) { |
| log.debug(Messages.getMessage("exception00"), e); |
| } |
| return; |
| } catch (InvocationTargetException e) { |
| if(log.isDebugEnabled()) { |
| log.debug(Messages.getMessage("exception00"), e); |
| } |
| return; |
| } |
| } |
| |
| // OK, go find any current OperationDescs for this method name and |
| // make sure they're synced with the actual class. |
| if (name2OperationsMap != null) { |
| ArrayList currentOverloads = |
| (ArrayList)name2OperationsMap.get(methodName); |
| if (currentOverloads != null) { |
| // For each one, sync it to the implementation class' methods |
| for (Iterator i = currentOverloads.iterator(); i.hasNext();) { |
| OperationDesc oper = (OperationDesc) i.next(); |
| if (oper.getMethod() == null) { |
| syncOperationToClass(oper, implClass); |
| } |
| } |
| } |
| } |
| |
| // Now all OperationDescs from deployment data have been completely |
| // filled in. So we now make new OperationDescs for any method |
| // overloads which were not covered above. |
| // NOTE : This is the "lenient" approach, which allows you to |
| // specify one overload and still get the others by introspection. |
| // We could equally well return above if we found OperationDescs, |
| // and have a rule that if you specify any overloads, you must specify |
| // all the ones you want accessible. |
| |
| createOperationsForName(implClass, methodName); |
| |
| // Note that we never have to look at this method name again. |
| completedNames.add(methodName); |
| } |
| |
| private String getUniqueOperationName(String name) { |
| int i = 1; |
| String candidate; |
| do { |
| candidate = name + i++; |
| } while (name2OperationsMap.get(candidate) != null); |
| |
| return candidate; |
| } |
| |
| /** |
| * Look for methods matching this name, and for each one, create an |
| * OperationDesc (if it's not already in our list). |
| * |
| * TODO: Make this more efficient |
| */ |
| private void createOperationsForName(Class implClass, String methodName) |
| { |
| // If we're a Skeleton deployment, skip the statics. |
| if (isSkeletonClass) { |
| if (methodName.equals("getOperationDescByName") || |
| methodName.equals("getOperationDescs")) |
| return; |
| } |
| |
| Method [] methods = getMethods(implClass); |
| |
| for (int i = 0; i < methods.length; i++) { |
| Method method = methods[i]; |
| if (Modifier.isPublic(method.getModifiers()) && |
| method.getName().equals(methodName) && |
| !isServiceLifeCycleMethod(implClass, method)) { |
| createOperationForMethod(method); |
| } |
| } |
| |
| Class superClass = implClass.getSuperclass(); |
| if (superClass != null && |
| !superClass.getName().startsWith("java.") && |
| !superClass.getName().startsWith("javax.") && |
| (stopClasses == null || |
| !stopClasses.contains(superClass.getName()))) { |
| createOperationsForName(superClass, methodName); |
| } |
| } |
| |
| /** |
| * Make an OperationDesc from a Java method. |
| * |
| * In the absence of deployment metadata, this code will introspect a |
| * Method and create an appropriate OperationDesc. If the class |
| * implements the Skeleton interface, we will use the metadata from there |
| * in constructing the OperationDesc. If not, we use parameter names |
| * from the bytecode debugging info if available, or "in0", "in1", etc. |
| * if not. |
| */ |
| private void createOperationForMethod(Method method) { |
| // If we've already got it, never mind |
| if (method2OperationMap.get(method) != null) { |
| return; |
| } |
| |
| Class [] paramTypes = method.getParameterTypes(); |
| |
| // And if we've already got an exact match (i.e. an override), |
| // never mind |
| |
| ArrayList overloads = name2OperationsMap == null ? null : |
| (ArrayList)name2OperationsMap.get(method.getName()); |
| if (overloads != null && !overloads.isEmpty()) { |
| // Search each OperationDesc that already has a Method |
| // associated with it, and check for parameter type equivalence. |
| for (int i = 0; i < overloads.size(); i++) { |
| OperationDesc op = (OperationDesc)overloads.get(i); |
| Method checkMethod = op.getMethod(); |
| if (checkMethod != null) { |
| Class [] others = checkMethod.getParameterTypes(); |
| if (paramTypes.length == others.length) { |
| int j = 0; |
| for (; j < others.length; j++) { |
| if (!others[j].equals(paramTypes[j])) |
| break; |
| } |
| // If we got all the way through, we have a match. |
| if (j == others.length) |
| return; |
| } |
| } |
| } |
| } |
| |
| boolean isWSICompliant = JavaUtils.isTrue( |
| AxisProperties.getProperty(Constants.WSIBP11_COMPAT_PROPERTY)); |
| |
| // Make an OperationDesc, fill in common stuff |
| OperationDesc operation = new OperationDesc(); |
| |
| // If we're WS-I compliant, we can't have overloaded operation names. |
| // If we find duplicates, we generate unique names for them and map |
| // those names to the correct Method. |
| String name = method.getName(); |
| if (isWSICompliant && name2OperationsMap != null) { |
| Collection methodNames = name2OperationsMap.keySet(); |
| name = JavaUtils.getUniqueValue(methodNames, name); |
| } |
| operation.setName(name); |
| String defaultNS = ""; |
| if (namespaceMappings != null && !namespaceMappings.isEmpty()) { |
| // If we have a default namespace mapping, require callers to |
| // use that namespace. |
| defaultNS = (String)namespaceMappings.get(0); |
| } |
| if(defaultNS.length() == 0) { |
| defaultNS = Namespaces.makeNamespace(method.getDeclaringClass().getName()); |
| } |
| operation.setElementQName(new QName(defaultNS, name)); |
| operation.setMethod(method); |
| |
| // If this is a MESSAGE style service, set up the OperationDesc |
| // appropriately. |
| if (style == Style.MESSAGE) { |
| int messageOperType = checkMessageMethod(method); |
| if(messageOperType == OperationDesc.MSG_METHOD_NONCONFORMING) return; |
| if (messageOperType == -1) { |
| throw new InternalException("Couldn't match method to any of the allowable message-style patterns!"); |
| } |
| operation.setMessageOperationStyle(messageOperType); |
| operation.setReturnClass(Object.class); |
| operation.setReturnType(Constants.XSD_ANYTYPE); |
| } else { |
| // For other styles, continue here. |
| Class retClass = method.getReturnType(); |
| operation.setReturnClass(retClass); |
| QName typeQName = getTypeQName(retClass); |
| operation.setReturnType(typeQName); |
| |
| String [] paramNames = getParamNames(method); |
| |
| for (int k = 0; k < paramTypes.length; k++) { |
| Class type = paramTypes[k]; |
| ParameterDesc paramDesc = new ParameterDesc(); |
| // param should be unqualified if we're using rpc style, |
| // or should use the operation's namespace if its document style |
| String paramNamespace = (this.style == Style.RPC ? "" : operation.getElementQName().getNamespaceURI()); |
| |
| // If we have a name for this param, use it, otherwise call |
| // it "in*" |
| if (paramNames != null && paramNames[k] != null && |
| paramNames[k].length()>0) { |
| paramDesc.setQName(new QName(paramNamespace, paramNames[k])); |
| } else { |
| paramDesc.setQName(new QName(paramNamespace, "in" + k)); |
| } |
| |
| // If it's a Holder, mark it INOUT, and set the XML type QName |
| // to the held type. Otherwise it's IN. |
| |
| Class heldClass = JavaUtils.getHolderValueType(type); |
| if (heldClass != null) { |
| paramDesc.setMode(ParameterDesc.INOUT); |
| paramDesc.setTypeQName(getTypeQName(heldClass)); |
| } else { |
| paramDesc.setMode(ParameterDesc.IN); |
| paramDesc.setTypeQName(getTypeQName(type)); |
| } |
| paramDesc.setJavaType(type); |
| operation.addParameter(paramDesc); |
| } |
| } |
| |
| createFaultMetadata(method, operation); |
| |
| addOperationDesc(operation); |
| method2OperationMap.put(method, operation); |
| } |
| |
| private QName getTypeQName(Class javaClass) { |
| QName typeQName; |
| TypeMapping tm = getTypeMapping(); |
| if (style == Style.RPC) { |
| typeQName = tm.getTypeQName(javaClass); |
| } else { |
| typeQName = tm.getTypeQNameExact(javaClass); |
| if (typeQName == null && javaClass.isArray()) { |
| typeQName = tm.getTypeQName(javaClass.getComponentType()); |
| } else { |
| typeQName = tm.getTypeQName(javaClass); |
| } |
| } |
| return typeQName; |
| } |
| |
| private void createFaultMetadata(Method method, OperationDesc operation) { |
| // Create Exception Types |
| Class[] exceptionTypes = method.getExceptionTypes(); |
| |
| for (int i=0; i < exceptionTypes.length; i++) { |
| // Every remote method declares a java.rmi.RemoteException |
| // Only interested in application specific exceptions. |
| // Ignore java and javax package exceptions. |
| Class ex = exceptionTypes[i]; |
| if (ex != java.rmi.RemoteException.class && |
| ex != org.apache.axis.AxisFault.class && |
| !ex.getName().startsWith("java.") && |
| !ex.getName().startsWith("javax.")) { |
| |
| // For JSR 101 v.1.0, there is a simple fault mapping |
| // and a complexType fault mapping...both mappings |
| // generate a class that extends (directly or indirectly) |
| // Exception. |
| // When converting java back to wsdl it is not possible |
| // to determine which way to do the mapping, |
| // so it is always mapped back using the complexType |
| // fault mapping because it is more useful (i.e. it |
| // establishes a hierarchy of exceptions). Note that this |
| // will not cause any roundtripping problems. |
| // Rich |
| |
| |
| /* Old Simple Type Mode |
| Field[] f = ex.getDeclaredFields(); |
| ArrayList exceptionParams = new ArrayList(); |
| for (int j = 0; j < f.length; j++) { |
| int mod = f[j].getModifiers(); |
| if (Modifier.isPublic(mod) && |
| !Modifier.isStatic(mod)) { |
| QName qname = new QName("", f[j].getName()); |
| QName typeQName = tm.getTypeQName(f[j].getType()); |
| ParameterDesc param = new ParameterDesc(qname, |
| ParameterDesc.IN, |
| typeQName); |
| param.setJavaType(f[j].getType()); |
| exceptionParams.add(param); |
| } |
| } |
| String pkgAndClsName = ex.getName(); |
| FaultDesc fault = new FaultDesc(); |
| fault.setName(pkgAndClsName); |
| fault.setParameters(exceptionParams); |
| operation.addFault(fault); |
| */ |
| |
| FaultDesc fault = operation.getFaultByClass(ex, false); |
| boolean isNew; |
| |
| // If we didn't find one, create a new one |
| if (fault == null) { |
| fault = new FaultDesc(); |
| isNew = true; |
| } else { |
| isNew = false; |
| } |
| |
| // Try to fil in any parts of the faultDesc that aren't there |
| |
| // XMLType |
| QName xmlType = fault.getXmlType(); |
| if (xmlType == null) { |
| fault.setXmlType(getTypeMapping().getTypeQName(ex)); |
| } |
| |
| // Name and Class Name |
| String pkgAndClsName = ex.getName(); |
| if (fault.getClassName() == null) { |
| fault.setClassName(pkgAndClsName); |
| } |
| if (fault.getName() == null) { |
| String name = pkgAndClsName.substring( |
| pkgAndClsName.lastIndexOf('.') + 1, |
| pkgAndClsName.length()); |
| fault.setName(name); |
| } |
| |
| // Parameters |
| // We add a single parameter which points to the type |
| if (fault.getParameters() == null) { |
| if (xmlType == null) { |
| xmlType = getTypeMapping().getTypeQName(ex); |
| } |
| QName qname = fault.getQName(); |
| if (qname == null) { |
| qname = new QName("", "fault"); |
| } |
| ParameterDesc param = new ParameterDesc( |
| qname, |
| ParameterDesc.IN, |
| xmlType); |
| param.setJavaType(ex); |
| ArrayList exceptionParams = new ArrayList(); |
| exceptionParams.add(param); |
| fault.setParameters(exceptionParams); |
| } |
| |
| // QName |
| if (fault.getQName() == null) { |
| fault.setQName(new QName(pkgAndClsName)); |
| } |
| |
| if (isNew) { |
| // Add the fault to the operation |
| operation.addFault(fault); |
| } |
| } |
| } |
| } |
| |
| private String[] getParamNames(Method method) { |
| synchronized (method2ParamsMap) { |
| String [] paramNames = (String []) method2ParamsMap.get(method); |
| if(paramNames != null) |
| return paramNames; |
| paramNames = ParamNameExtractor.getParameterNamesFromDebugInfo(method); |
| method2ParamsMap.put(method, paramNames); |
| return paramNames; |
| } |
| } |
| |
| public void setNamespaceMappings(List namespaces) { |
| namespaceMappings = namespaces; |
| } |
| |
| public String getDefaultNamespace() { |
| if (namespaceMappings == null || namespaceMappings.isEmpty()) |
| return null; |
| return (String)namespaceMappings.get(0); |
| } |
| |
| public void setDefaultNamespace(String namespace) { |
| if (namespaceMappings == null) |
| namespaceMappings = new ArrayList(); |
| namespaceMappings.add(0, namespace); |
| } |
| |
| public void setProperty(String name, Object value) { |
| if (properties == null) { |
| properties = new HashMap(); |
| } |
| properties.put(name, value); |
| } |
| |
| public Object getProperty(String name) { |
| if (properties == null) |
| return null; |
| |
| return properties.get(name); |
| } |
| |
| public String getEndpointURL() { |
| return endpointURL; |
| } |
| |
| public void setEndpointURL(String endpointURL) { |
| this.endpointURL = endpointURL; |
| } |
| |
| public TypeMappingRegistry getTypeMappingRegistry() { |
| if (tmr == null) { |
| tmr = new TypeMappingRegistryImpl(false); |
| } |
| return tmr; |
| } |
| |
| public void setTypeMappingRegistry(TypeMappingRegistry tmr) { |
| this.tmr = tmr; |
| } |
| |
| public boolean isInitialized() { |
| return implClass != null; |
| } |
| } |