/*
* The Apache Software License, Version 1.1
*
*
* Copyright (c) 2002 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Axis" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
package org.apache.axis.description;
import org.apache.axis.AxisServiceConfig;
import org.apache.axis.InternalException;
import org.apache.axis.components.logger.LogFactory;
import org.apache.axis.encoding.DefaultTypeMappingImpl;
import org.apache.axis.encoding.TypeMapping;
import org.apache.axis.encoding.TypeMappingRegistry;
import org.apache.axis.encoding.TypeMappingRegistryImpl;
import org.apache.axis.enum.Style;
import org.apache.axis.utils.JavaUtils;
import org.apache.axis.utils.bytecode.ParamNameExtractor;
import org.apache.axis.wsdl.Skeleton;
import org.apache.commons.logging.Log;
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.Collection;
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 ServiceDesc {
protected static Log log =
LogFactory.getLog(ServiceDesc.class.getName());
/** The name of this service */
private String name = null;
/** List of allowed methods */
/** null allows everything, an empty ArrayList allows nothing */
private List allowedMethods = null;
/** List if disallowed methods */
private List disallowedMethods = null;
/** Style */
private Style style = Style.RPC;
/** Implementation class */
private Class implClass = null;
/** 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;
/**
* 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 Method skelMethod = null;
/** Classes at which we should stop looking up the inheritance chain
* when introspecting
*/
private ArrayList stopClasses = null;
/** Lookup caches */
private HashMap name2OperationsMap = null;
private HashMap qname2OperationsMap = null;
private HashMap method2OperationMap = new HashMap();
/** 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 = DefaultTypeMappingImpl.getSingleton();
private TypeMappingRegistry tmr = null;
private boolean haveAllSkeletonMethods = false;
private boolean introspectionComplete = false;
/**
* Default constructor
*/
public ServiceDesc() {
}
/**
* What kind of service is this?
* @return
*/
public Style getStyle() {
return style;
}
public void setStyle(Style style) {
this.style = style;
}
/**
* 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(
JavaUtils.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) {
return;
} catch (IllegalArgumentException e) {
return;
} catch (InvocationTargetException e) {
return;
}
haveAllSkeletonMethods = true;
}
public TypeMapping getTypeMapping() {
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;
}
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 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);
}
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)
{
// If we're MESSAGE style, we should only have a single operation,
// to which we'll pass any XML we receive.
if (style == Style.MESSAGE) {
if (!introspectionComplete) {
loadServiceDescByIntrospection();
}
if (operations.size() > 0)
return new OperationDesc [] { (OperationDesc)operations.get(0) };
return null;
}
// If we're DOCUMENT style, we look in our mapping of QNames ->
// operations instead. But first, let's make sure we've initialized
// said mapping....
initQNameMap();
ArrayList overloads = (ArrayList)qname2OperationsMap.get(qname);
if (overloads == null) {
if ((style == Style.RPC) && (name2OperationsMap != null)) {
// Try ignoring the namespace....?
overloads = (ArrayList)name2OperationsMap.get(qname.getLocalPart());
}
if (overloads == null)
return null;
}
getSyncedOperationsForName(implClass,
((OperationDesc)overloads.get(0)).getName());
OperationDesc [] array = new OperationDesc [overloads.size()];
return (OperationDesc[])overloads.toArray(array);
}
private synchronized void initQNameMap() {
if (qname2OperationsMap == null) {
loadServiceDescByIntrospection();
qname2OperationsMap = new HashMap();
for (Iterator i = operations.iterator(); i.hasNext();) {
OperationDesc operationDesc = (OperationDesc) i.next();
ArrayList list =
(ArrayList)qname2OperationsMap.get(operationDesc.
getElementQName());
if (list == null) {
list = new ArrayList();
qname2OperationsMap.put(operationDesc.getElementQName(),
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 = implClass.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
if (Modifier.isPublic(method.getModifiers()) &&
method.getName().equals(oper.getName())) {
// Check params
Class [] paramTypes = method.getParameterTypes();
if (paramTypes.length != oper.getNumParams())
continue;
int j;
for (j = 0; j < paramTypes.length; j++) {
Class type = paramTypes[j];
Class heldType = type;
if (Holder.class.isAssignableFrom(type)) {
heldType = 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 held 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 = tm.getTypeQName(heldType);
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 = tm.getClassForQName(param.getTypeQName());
}
// 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, heldType)) {
break;
}
}
// 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;
}
oper.setReturnClass(method.getReturnType());
// At some point we might want to check here to see if this
// Method is already associated with another Operation, but
// this doesn't seem critital.
oper.setMethod(method);
method2OperationMap.put(method, 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(JavaUtils.getMessage("serviceDescOperSync00",
oper.getName(),
implClass.getName()));
throw ie;
}
}
/**
* 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);
introspectionComplete = true;
}
/**
* Recursive helper class for loadServiceDescByIntrospection
*/
private void loadServiceDescByIntrospectionRecursive(Class implClass)
{
if (Skeleton.class.equals(implClass)) {
return;
}
Method [] methods = implClass.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
if (Modifier.isPublic(methods[i].getModifiers())) {
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 (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 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) {
return;
} catch (IllegalArgumentException e) {
return;
} catch (InvocationTargetException 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);
}
/**
* 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)
{
Method [] methods = implClass.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
if (Modifier.isPublic(method.getModifiers()) &&
method.getName().equals(methodName)) {
createOperationForMethod(method);
}
}
Class superClass = implClass.getSuperclass();
if (superClass != null &&
!superClass.getName().startsWith("java.") &&
!superClass.getName().startsWith("javax.")) {
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;
}
}
}
}
// Make an OperationDesc, fill in common stuff
OperationDesc operation = new OperationDesc();
operation.setName(method.getName());
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);
}
operation.setElementQName(new QName(defaultNS, method.getName()));
operation.setMethod(method);
Class retClass = method.getReturnType();
operation.setReturnClass(retClass);
operation.setReturnType(tm.getTypeQName(method.getReturnType()));
String [] paramNames =
ParamNameExtractor.getParameterNamesFromDebugInfo(method);
for (int k = 0; k < paramTypes.length; k++) {
Class type = paramTypes[k];
ParameterDesc paramDesc = new ParameterDesc();
// If we have a name for this param, use it, otherwise call
// it "in*"
if (paramNames != null) {
paramDesc.setName(paramNames[k]);
} else {
paramDesc.setName("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(tm.getTypeQName(heldClass));
} else {
paramDesc.setMode(ParameterDesc.IN);
paramDesc.setTypeQName(tm.getTypeQName(type));
}
paramDesc.setJavaType(type);
operation.addParameter(paramDesc);
}
// Create Exception Types
Class[] exceptionTypes = new Class[method.getExceptionTypes().length];
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);
*/
// Create a single part with the dummy name "fault"
// that locates the complexType for this exception.
ParameterDesc param = new ParameterDesc(
new QName("", "fault"),
ParameterDesc.IN,
tm.getTypeQName(ex));
param.setJavaType(ex);
ArrayList exceptionParams = new ArrayList();
exceptionParams.add(param);
String pkgAndClsName = ex.getName();
FaultDesc fault = new FaultDesc();
fault.setName(pkgAndClsName);
fault.setParameters(exceptionParams);
operation.addFault(fault);
}
}
addOperationDesc(operation);
method2OperationMap.put(method, operation);
}
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();
}
return tmr;
}
public void setTypeMappingRegistry(TypeMappingRegistry tmr) {
this.tmr = tmr;
}
}