/*
* 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.beehive.wsm.axis;
import org.apache.axis.constants.Style;
import org.apache.axis.constants.Use;
import org.apache.axis.description.*;
import org.apache.axis.encoding.*;
import org.apache.axis.encoding.ser.ArrayDeserializerFactory;
import org.apache.axis.encoding.ser.ArraySerializerFactory;
import org.apache.axis.encoding.ser.BeanDeserializerFactory;
import org.apache.axis.encoding.ser.BeanSerializerFactory;
import org.apache.axis.utils.BeanPropertyDescriptor;
import org.apache.axis.utils.BeanUtils;
import org.apache.axis.utils.JavaUtils;
import org.apache.axis.wsdl.fromJava.Namespaces;
import org.apache.axis.wsdl.fromJava.Types;
import org.apache.beehive.wsm.axis.databinding.SystemTypeLookupService;
import org.apache.beehive.wsm.axis.registration.AxisTypeMappingMetaData;
import org.apache.beehive.wsm.axis.util.encoding.XmlBeanDeserializerFactory;
import org.apache.beehive.wsm.axis.util.encoding.XmlBeanSerializerFactory;
import org.apache.beehive.wsm.databinding.BindingLookupService;
import org.apache.beehive.wsm.model.BeehiveWsMethodMetadata;
import org.apache.beehive.wsm.model.BeehiveWsParameterMetadata;
import org.apache.beehive.wsm.model.BeehiveWsSOAPBindingInfo;
import org.apache.beehive.wsm.model.BeehiveWsTypeMetadata;
import org.apache.beehive.wsm.util.InvalidTypeMappingException;
import org.apache.beehive.wsm.registration.TypeRegistrar;
import org.apache.log4j.Logger;
import org.apache.xmlbeans.XmlBeans;
import org.apache.xmlbeans.XmlObject;
import javax.jws.WebParam;
import javax.jws.soap.SOAPBinding;
import javax.wsdl.OperationType;
import javax.xml.namespace.QName;
import javax.xml.rpc.holders.Holder;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.rmi.Remote;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* ****************************************************************************
*
* @author Jonathan Colwell
*/
public class AxisHook {
static Logger logger = Logger.getLogger(AxisHook.class);
public static ServiceDesc createServiceDesc(BeehiveWsTypeMetadata wsm,
ClassLoader cl) throws ClassNotFoundException,
NoSuchMethodException, InvalidTypeMappingException {
JavaServiceDesc sd = new JavaServiceDesc();
if (null == cl) {
/*
* NOTE jcolwell@bea.com 2004-Aug-30 -- if no classloader was
* provided, use the one that loaded this Class
*/
cl = AxisHook.class.getClassLoader();
}
final Class serviceClass = cl.loadClass(wsm.getClassName());
// Create a list of the allowed methods
List<String> allowedMethods = new ArrayList<String>();
for (BeehiveWsMethodMetadata meth : wsm.getMethods()) {
String method = meth.getJavaMethodName();
allowedMethods.add(method);
}
// set the ServiceDesc base information
sd.setName(wsm.getWsName());
sd.setImplClass(serviceClass);
String targetNamespace = wsm.getWsTargetNamespace();
sd.setDefaultNamespace(targetNamespace);
sd.setAllowedMethods(allowedMethods);
configureSoapBinding(sd, wsm.getSoapBinding());
TypeMappingRegistry tmr = new TypeMappingRegistryImpl(true);
TypeMapping tm = tmr
.getOrMakeTypeMapping(sd.getUse() == Use.ENCODED ? "http://schemas.xmlsoap.org/soap/encoding/" //"encoded"
: "");
sd.setTypeMappingRegistry(tmr);
sd.setTypeMapping(tm);
/*
* jongjinchoi@apache.org 2005-Mar-16 -- Use Axis's introspection
* feature instead of creating new OperationDesc and ParameterDescs
* directly. The introspected OperationDesc and ParameterDescs are
* overrided by WSM. When appropriate type mapping registry is set, Axis
* fills the ParameterDesc's typeEntry from the preset typemapping
* registry, which is required for Axis to work in wrapped/lit mode.
*/
sd.getOperations();
// Walk the methods
for (BeehiveWsMethodMetadata meth : wsm.getMethods()) {
String operationName = meth.getWmOperationName();
if (null != operationName && 0 < operationName.length()) {
// set the Operations properties
OperationDesc od = sd.getOperationByName(meth
.getJavaMethodName());
od.setElementQName(new QName(targetNamespace, operationName));
od.setName(operationName);
od.setSoapAction(meth.getWmAction());
if (meth.isOneWay()) {
od.setMep(OperationType.ONE_WAY);
} else {
String namespace = "" ;
// namespace only should be added for document style, RPC style doesn't need name space for return type.
if( wsm.getSoapBinding().getStyle() == SOAPBinding.Style.DOCUMENT) namespace = meth.getWrTargetNamespace();
od.setReturnQName(new QName(namespace,
meth.getWrName()));
final Class returnType = meth.getJavaReturnType();
QName qn = configureTypeMapping(sd, returnType, meth
.getWrTargetNamespace());
od.setReturnType(qn);
od.setReturnClass(returnType);
}
// process the parameters
int pcnt = 0;
for (BeehiveWsParameterMetadata param : meth.getParams()) {
ParameterDesc pd = od.getParameter(pcnt++);
final Class paramType = param.getJavaType();
if (pd.getTypeQName() == null) { // set the typeQName if
// it is not set
// already.
QName typeQName = configureTypeMapping(sd, paramType,
param.getWpTargetNamespace());
/*
* jongjinchoi@apache.org 2005-Mar-16 -- The typeQName
* from configureTypeMapping() is not dummy. This is
* required to find ArrayDeserializer when the
* document/literal bare array is deserialized.
*/
pd.setTypeQName(typeQName);
}
// set QName
String namespace = "" ;
// namespace only should be added for document style, RPC style doesn't need name space for parameter names.
if( wsm.getSoapBinding().getStyle() == SOAPBinding.Style.DOCUMENT) namespace = param.getWpTargetNamespace();
QName paramQName = new QName(namespace,
param.getWpName());
pd.setQName(paramQName);
// set Mode
final boolean header = param.isWpHeader();
final WebParam.Mode mode = param.getWpMode();
switch (mode) {
case IN:
pd.setMode(ParameterDesc.IN);
pd.setInHeader(header);
pd.setOutHeader(false);
break;
case OUT:
pd.setMode(ParameterDesc.OUT);
pd.setInHeader(false);
pd.setOutHeader(header);
break;
case INOUT:
pd.setMode(ParameterDesc.INOUT);
pd.setInHeader(header);
pd.setOutHeader(header);
break;
default:
throw new IllegalArgumentException(
"Illegal value for WebParam.Mode: " + mode);
}
// set JavaType
pd.setJavaType(paramType);
}
// set Exceptions
Method javaMethod = od.getMethod();
for (Class thrown : javaMethod.getExceptionTypes()) {
FaultDesc fd = od.getFaultByClass(thrown);
if (null == fd) {
logger
.info("Exception: "
+ thrown.getCanonicalName()
+ " is not picked up by the Axis, only non Remote and Application Specific exceptions are registed in Axis. This is not a fatal error.");
continue;
}
QName qname = configureTypeMapping(sd, thrown, meth
.getWrTargetNamespace());
fd.setXmlType(qname);
fd.setQName(qname);
fd.setComplex(true);
}
}
}
return sd;
}
/**
* This method will return a boolean value indicating that Activation is
* enabled. Activation requires the DataHandler and the Multipart Classes to
* both be found.
*
* @return boolean indicating that Activation is enabled.
*/
private static boolean isActivationEnabled() {
return null != getDataHandlerClass() && null != getMultipartClass();
}
/**
* This will return the Class for the DataHandler. This will return null if
* the DataHandler is not available.
*
* @return The DataHandler Class or null if the DataHandler is not found
*/
private static Class getDataHandlerClass() {
try {
return AxisHook.class.getClassLoader().loadClass(
"javax.activation.DataHandler");
} catch (ClassNotFoundException e) {
// ignore the class was not found
}
return null;
}
/**
* This will return the Class for the MimeMultipart handler. It will return
* null if the MimMultipart class is not available.
*
* @return The MimeMultipart Class or null if the DataHandler is not found.
*/
private static Class getMultipartClass() {
try {
return AxisHook.class.getClassLoader().loadClass(
"javax.mail.internet.MimeMultipart");
} catch (ClassNotFoundException e) {
// ignore the class was not found
}
return null;
}
private static QName configureTypeMapping(ServiceDesc desc, Class type,
String defaultNameSpace) throws InvalidTypeMappingException {
try {
if (Void.TYPE.equals(type))
return null;
// // If type is holder or it is generic holder, strip it to its base type
// type = TypeRegistrar.getUnderlyingType(type);
//
if (AxisTypeMappingMetaData.isBuiltInType(type))
return AxisTypeMappingMetaData.getBuiltInTypeQname(type);
if(Holder.class.isAssignableFrom(type )) {
type = TypeRegistrar.getHoldersValueClass(type);
// NOTE: May need to register the holder type also.
}
// if type needs to be registered
TypeMapping tm = desc.getTypeMapping();
// QName q = tm.getTypeQName(type);
// System.out.println("###################################type: "
// + type.getCanonicalName() + " qname is: " + q
// + " default namesapce: " + defaultNameSpace);
// if (null == q || // Not registered
// !q.getNamespaceURI().equals(defaultNameSpace)) { // registered
// // but not
// // in
// // current
// // namespace
// // q = generateQName(type, desc);
// q = new QName(defaultNameSpace, Types
// .getLocalNameFromFullName(type.getName()));
// System.out
// .println("CREATE QNAME: #############################type: "
// + type.getCanonicalName() + " qname is: " + q);
// }
BindingLookupService lookupService = new SystemTypeLookupService(); // move this to the constructor
QName q = lookupService.class2qname(type, defaultNameSpace);
if (type.isArray()) {
/*
* jongjinchoi@apache.org 2005-Mar-16 -- don't register array
* serializer in document(bare or wrapped)/literal mode.
*/
if (!tm.isRegistered(type, q) && desc.getStyle() == Style.RPC
&& desc.getUse() == Use.ENCODED) {
tm.register(type, q, new ArraySerializerFactory(type, q),
new ArrayDeserializerFactory());
}
QName qcomp = configureTypeMapping(desc, type
.getComponentType(), defaultNameSpace);
if (desc.getUse() == Use.LITERAL) {
q = qcomp;
}
} else if (!tm.isRegistered(type, q)) {
if (XmlObject.class.isAssignableFrom(type)) {
q = XmlBeans.typeForClass(type).getName();
tm.register(type, q, new XmlBeanSerializerFactory(type, q),
new XmlBeanDeserializerFactory(type, q));
}
/*
* NOTE jcolwell@bea.com 2004-Oct-11 -- these datahandler using
* classes are generally already registered but just in case...
*/
else if (isActivationEnabled()
&& (java.awt.Image.class.isAssignableFrom(type)
|| getMultipartClass().isAssignableFrom(type) || getDataHandlerClass()
.isAssignableFrom(type))) {
try {
/*
* NOTE jcolwell@bea.com 2004-Oct-08 -- doing reflection
* here in case AXIS was built without attachment
* support.
*/
ClassLoader cl = AxisHook.class.getClassLoader();
// Loadclass could have been done in import also, but if
// there are no activation.jar then this would
// cause error when the class is loaded. To prevent that
// we load the class explicitly at this point.
// if we had done the "new
// org.apache.axis.encoding.ser.JAFDataHandlerSerializerFactory"
// then the class
// would have had dependecies to the org.apache... class
// which would not have worked in case activation was
// not on the path.
Class<SerializerFactory> sfClass = (Class<SerializerFactory>) cl
.loadClass("org.apache.axis.encoding.ser.JAFDataHandlerSerializerFactory");
Class<DeserializerFactory> dsfClass = (Class<DeserializerFactory>) cl
.loadClass("org.apache.axis.encoding.ser.JAFDataHandlerDeserializerFactory");
Constructor<SerializerFactory> sfCon = sfClass
.getConstructor(Class.class, QName.class);
Constructor<DeserializerFactory> dsfCon = dsfClass
.getConstructor(Class.class, QName.class);
SerializerFactory sf = sfCon.newInstance(type, q);
DeserializerFactory dsf = dsfCon.newInstance(type, q);
tm.register(type, q, sf, dsf);
} catch (Exception e) {
/*
* FIXME jcolwell@bea.com 2004-Oct-08 -- log this
* properly
*/
e.printStackTrace();
}
} else if (!Remote.class.isAssignableFrom(type)
/*
* NOTE jcolwell@bea.com 2004-Dec-01 -- java.rmi.Remote is
* prohibited by the jax-rpc spec
*
* NOTE jcolwell@bea.com 2004-Oct-11 -- restricting against
* File, since it doesn't make sense to serialize as a bean. It
* causes an infinite loop as it keeps returning itself from the
* getAbsoluteFile and getCanonicalFile calls
*/
&& !File.class.isAssignableFrom(type)) {
TypeDesc td = TypeDesc.getTypeDescForClass(type);
// if type was registered in a different namespace, then
// ignore this and create a new td
if (td != null
&& !td.getXmlType().getNamespaceURI().equals(
q.getNamespaceURI())) {
td = null;
}
TypeDesc superTd = null;
BeanPropertyDescriptor[] superPd = null;
// type desc is used for java-xml mapping, make sure the
// class and all its super classes have a type desc defined.
if (null == td) {
td = new TypeDesc(type); // create type descriptor
// for this class --- NOT
// its super classes at this
// point.
// add super class types.
Class supa = type.getSuperclass();
if ((supa != null) && (supa != java.lang.Object.class)
&& (supa != java.lang.Exception.class)
&& (supa != java.lang.Throwable.class)
&& (supa != java.rmi.RemoteException.class)
&& (supa != org.apache.axis.AxisFault.class)) {
configureTypeMapping(desc, supa, defaultNameSpace);
}
// check to see if a type mapping was created for the
// super class.
superTd = TypeDesc.getTypeDescForClass(supa);
if (superTd != null) // super class is a regular java
// bean with axis typedesc.
{
superPd = superTd.getPropertyDescriptors(); // this
// is
// mapping
// for
// all
// my
// super
// classes.
}
td.setXmlType(q);
TypeDesc.registerTypeDescForClass(type, td);
// NOTE: this is partially finished td, so more
// processing to follow that is why its td is not set to
// null as it is
// for the case when it is already provided (when td
// !=null)
} else {
td = null; // we don't need type desc. any more this is
// a complete td
}
//
// // At this all parent bean classes and their properties
// // (attributes) have been registered with typedecriptor
// and
// // type mapping.
// // next regidster type for this class.
// tm.register(type, q, new BeanSerializerFactory(type, q),
// /*
// * NOTE jcolwell@bea.com 2004-Oct-11 -- should check that
// * the type to deserialize has a default contructor but
// with
// * this setup there is no way to know if it is used only
// in
// * serialization.
// */
// new BeanDeserializerFactory(type, q));
// At this all parent bean classes and their properties
// (attributes) have been registered with typedecriptor and
// type mapping.
// next regidster type for this class.
tm.register(type, q, new EnhancedBeanSerializerFactory(
type, q, td),
/*
* NOTE jcolwell@bea.com 2004-Oct-11 -- should check that
* the type to deserialize has a default contructor but with
* this setup there is no way to know if it is used only in
* serialization.
*/
new EnhancedBeanDeSerializerFactory(type, q, td));
// now register the types for this bean properties
// (attributes)
// Note: we have to consider the case that one of the
// properties may be XML bean
// or a class that can deal with its own serialization.
Map serProps = BeanDeserializerFactory.getProperties(type,
null); // Note this is all of the bean properties
for (BeanPropertyDescriptor beanProps : (Collection<BeanPropertyDescriptor>) serProps
.values()) {
Class subType = beanProps.getType();
// make sure the property type is configred with Type
// mapping and its serializer information
if (!(subType.isPrimitive()
|| subType.getName().startsWith("java.") || subType
.getName().startsWith("javax."))) {
configureTypeMapping(desc, subType,
defaultNameSpace); // if this was XML bean
// this recursion would
// take care of it
}
if (td != null) { // if I didn't have type descriptor
// when I came to this method... I
// created partially filled one
// above
// now need to complete this.
String ns = q.getNamespaceURI(); // name space
// for the class
// if there is
// no parent
// find proper namespace for this element... we need
// to find out whihc class in the hierarchy the
// element came from
// once you know where the element came form (which
// class) then you know the element's name space.
if (superTd != null && superPd != null) { // if I
// had a
// parent,
for (int j = 0; j < superPd.length; j++) {
if (beanProps.getName().equals(
superPd[j].getName())) {
ns = superTd.getXmlType()
.getNamespaceURI();
break;
}
}
}
FieldDesc fd = new ElementDesc();
fd.setJavaType(subType);
fd.setFieldName(beanProps.getName());
fd.setXmlName(new QName(ns, beanProps.getName()));
// NOTE jcolwell@bea.com 2004-Oct-28 -- might need
// to do more to ensure a useful type QName.
fd.setXmlType(tm.getTypeQName(subType));
((ElementDesc) fd).setNillable(true);
// mmerz@apache.com 2005-Mar-09: required since Axis
// 1.2RC3 to allow for null values in complex
// objects; note that there is no (JSR-181)
// annotation that allows configuring this value.
td.addFieldDesc(fd);
}
}
} else {
throw new InvalidTypeMappingException("failed to register "
+ type.getName()
+ " as a valid web service datatype,"
+ " consider using a custom type mapping");
}
}
return q;
} catch (RuntimeException e) {
logger.error("Error in type registeration", e);
e.printStackTrace();
throw e;
}
}
private static QName generateQName(Class type, ServiceDesc desc) {
String namespace = Namespaces.makeNamespace(type.getName());
if (namespace == null || namespace.endsWith("DefaultNamespace")) {
namespace = desc.getDefaultNamespace();
}
return new QName(namespace, Types.getLocalNameFromFullName(type
.getName()));
}
protected static void configureSoapBinding(ServiceDesc sd,
BeehiveWsSOAPBindingInfo sbi) {
javax.jws.soap.SOAPBinding.Style style = javax.jws.soap.SOAPBinding.Style.DOCUMENT;
javax.jws.soap.SOAPBinding.Use use = javax.jws.soap.SOAPBinding.Use.LITERAL;
javax.jws.soap.SOAPBinding.ParameterStyle paramStyle = javax.jws.soap.SOAPBinding.ParameterStyle.WRAPPED;
if (sbi != null) {
style = sbi.getStyle();
use = sbi.getUse();
paramStyle = sbi.getParameterStyle();
}
if (style == javax.jws.soap.SOAPBinding.Style.RPC) {
sd.setStyle(Style.RPC);
if (use == javax.jws.soap.SOAPBinding.Use.ENCODED) {
sd.setUse(Use.ENCODED);
} else {
sd.setUse(Use.LITERAL);
}
} else {
/*
* since DOCUMENT ENCODED is not valid so force to use LITERAL
*/
sd.setUse(Use.LITERAL);
// check if this is a wrapped document literal
if (paramStyle == javax.jws.soap.SOAPBinding.ParameterStyle.WRAPPED) {
sd.setStyle(Style.WRAPPED);
} else {
// just regular document style
sd.setStyle(Style.DOCUMENT);
}
}
}
}
/*
* A TEMP SOLUTION TO BEAN Serialization/Deserialization problem The problem is
* that the Axis factories use the types that are defined for a class, so a
* given class can't be in multiple name spaces. In this solution the factory
* gets the type descriptor in the constructor
*/
class EnhancedBeanSerializerFactory extends BeanSerializerFactory {
public EnhancedBeanSerializerFactory(Class javaType, QName xmlType,
TypeDesc typeDesc) {
super(javaType, xmlType);
this.typeDesc = typeDesc;
if (typeDesc != null) {
propertyDescriptor = typeDesc.getPropertyDescriptors();
} else {
propertyDescriptor = BeanUtils.getPd(javaType, null);
}
}
}
class EnhancedBeanDeSerializerFactory extends BeanDeserializerFactory {
public EnhancedBeanDeSerializerFactory(Class javaType, QName xmlType,
TypeDesc typeDesc) {
super(javaType, xmlType);
this.typeDesc = typeDesc;
propertyMap = getProperties(javaType, typeDesc);
}
}