blob: efe9e00475436986bcc5345633eecc48fd1c787a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.axis2.jaxws.marshaller.impl.alt;
import org.apache.axis2.jaxws.ExceptionFactory;
import org.apache.axis2.jaxws.description.FaultDescription;
import org.apache.axis2.jaxws.i18n.Messages;
import org.apache.axis2.jaxws.runtime.description.marshal.FaultBeanDesc;
import org.apache.axis2.jaxws.runtime.description.marshal.MarshalServiceRuntimeDescription;
import org.apache.axis2.jaxws.utility.PropertyDescriptorPlus;
import org.apache.axis2.jaxws.wrapper.JAXBWrapperTool;
import org.apache.axis2.jaxws.wrapper.impl.JAXBWrapperToolImpl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.xml.ws.WebServiceException;
import java.beans.IntrospectionException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* The JAX-WS Specification (chapter 3.7) indicates that JAX-WS
* supports exceptions that do not match the normal pattern (the normal
* pattern is defined in chapter 2.5.
*
* These non-matching exceptions are the result of running WSGen
* on a pre-existing webservice. I am going to use the term, legacy exception,
* to describe these non-matching exceptions.
*
* The JAX-WS marshaller (server) must marshal a legacy exception thrown from
* the web service impl. The marshalling/mapping algorithm is defined in chapter 3.7.
*
* On the client, the JAX-WS engine will need to demarshal exceptions. However
* the specification assumes that the client is always created via wsimport; therefore
* the assumption is that all exceptions on the client are compliant (never legacy exceptions).
* I have included some code here in case we have to deal with legacy exceptions on the client...this is non-spec.
*
*
*/
class LegacyExceptionUtil {
private static Log log = LogFactory.getLog(LegacyExceptionUtil.class);
private static Set<String> ignore = new HashSet<String>();
static {
// Per Chap 3.7 rule 3, ignore these properties on the exception
ignore.add("localizedMessage");
ignore.add("stackTrace");
ignore.add("class");
ignore.add("cause");
}
/** Static class. Constructor is intentionally private */
private LegacyExceptionUtil() {
}
/**
* Create a FaultBean populated with the data from the Exception t The algorithm used to
* populate the FaultBean is described in JAX-WS 3.7
*
* @param t
* @param fd
* @param marshalDesc
* @return faultBean
*/
static Object createFaultBean(Throwable t, FaultDescription fd,
MarshalServiceRuntimeDescription marshalDesc)
throws WebServiceException {
Object faultBean = null;
try {
// Get the fault bean name from the fault description.
// REVIEW The default name should be:
// Package = <SEI package> or <SEI package>.jaxws
// Name = <exception name> + Bean
FaultBeanDesc faultBeanDesc = marshalDesc.getFaultBeanDesc(fd);
String faultBeanName = faultBeanDesc.getFaultBeanClassName();
// TODO Add check that faultBeanName is correct
if (log.isDebugEnabled()) {
log.debug("Legacy Exception FaultBean name is = " + faultBeanName);
}
// Load the FaultBean Class
Class faultBeanClass = null;
if (faultBeanName != null && faultBeanName.length() > 0) {
try {
try {
faultBeanClass = MethodMarshallerUtils.loadClass(faultBeanName);
} catch (ClassNotFoundException e){
faultBeanClass = MethodMarshallerUtils.loadClass(faultBeanName, fd.getOperationDescription().getEndpointInterfaceDescription().getEndpointDescription().getAxisService().getClassLoader());
}
} catch (Throwable throwable) {
if (log.isDebugEnabled()) {
log.debug("Cannot load fault bean class = " + faultBeanName +
". Fallback to using the exception object");
}
}
}
if (faultBeanClass != null) {
// Get the properties names from the exception class
Map<String, PropertyDescriptorPlus> pdMap =
marshalDesc.getPropertyDescriptorMap(t.getClass());
// We need to assign the legacy exception data to the java bean class.
// We will use the JAXBWrapperTool.wrap utility to do this.
// Get the map of child objects
Map<String, Object> childObjects = getChildObjectsMap(t, pdMap);
Map<String, Class> declaringClass = new HashMap<String, Class>();
List<String> childNames = new ArrayList<String>(childObjects.keySet());
if (log.isErrorEnabled()) {
log.debug("List of properties on the Legacy Exception is " + childNames);
}
// Use the wrapper tool to get the child objects.
JAXBWrapperTool wrapperTool = new JAXBWrapperToolImpl();
Map<String, PropertyDescriptorPlus> pdMapForBean =
marshalDesc.getPropertyDescriptorMap(faultBeanClass);
faultBean =
wrapperTool.wrap(faultBeanClass, childNames, childObjects, declaringClass, pdMapForBean);
if (log.isErrorEnabled()) {
log.debug("Completed creation of the fault bean.");
}
} else {
throw ExceptionFactory.makeWebServiceException(
Messages.getMessage("faultProcessingNotSupported",t.getClass().getName()));
}
} catch (Exception e) {
throw ExceptionFactory.makeWebServiceException(e);
}
return faultBean;
}
/**
* Create an Exception using the data in the JAXB object. The specification is silent on this
* issue.
*
* @param exceptionClass
* @param jaxb
* @param marshalDesc
* @return
*/
static Exception createFaultException(Class exceptionClass,
Object jaxb,
MarshalServiceRuntimeDescription marshalDesc) {
Exception e = null;
try {
if (log.isErrorEnabled()) {
log.debug("Create Legacy Exception for " + exceptionClass.getName());
}
// Get the properties names from the exception class
Map<String, PropertyDescriptorPlus> pdMap =
marshalDesc.getPropertyDescriptorMap(exceptionClass);
// Now get a list of PropertyDescriptorPlus objects that map to the jaxb bean properties
Iterator<Entry<String, PropertyDescriptorPlus>> it = pdMap.entrySet().iterator();
List<PropertyDescriptorPlus> piList = new ArrayList<PropertyDescriptorPlus>();
while (it.hasNext()) {
Entry<String, PropertyDescriptorPlus> entry = it.next();
String propertyName = entry.getValue().getPropertyName();
// Some propertyNames should be ignored.
if (!ignore.contains(propertyName)) {
piList.add(entry.getValue());
}
}
// Find a matching constructor
List<String> childNames = new ArrayList<String>();
if (log.isErrorEnabled()) {
log.debug("List of childNames on legacy exception is " + childNames);
}
Constructor constructor = findConstructor(exceptionClass, piList, childNames);
if (log.isErrorEnabled()) {
log.debug("The constructor used to create the exception is " + constructor);
}
// Use the wrapper tool to unwrap the jaxb object
JAXBWrapperTool wrapperTool = new JAXBWrapperToolImpl();
Map<String, PropertyDescriptorPlus> pdMapForBean =
marshalDesc.getPropertyDescriptorMap(jaxb.getClass());
Object[] childObjects = wrapperTool.unWrap(jaxb, childNames, pdMapForBean);
if (log.isErrorEnabled()) {
log.debug("Calling newInstance on the constructor " + constructor);
}
e = (Exception)constructor.newInstance(childObjects);
} catch (Exception ex) {
throw ExceptionFactory.makeWebServiceException(ex);
}
return e;
}
/**
* Find a construcor that matches this set of properties
*
* @param cls
* @param pdList
* @param childNames returned in the order that they occur in the constructor
* @return Constructor or null
*/
private static Constructor findConstructor(Class cls, List<PropertyDescriptorPlus> pdList,
List<String> childNames) {
Constructor[] constructors = cls.getConstructors();
Constructor constructor = null;
if (constructors != null) {
for (int i = 0; i < constructors.length && constructor == null; i++) {
Constructor tryConstructor = constructors[i];
if (tryConstructor.getParameterTypes().length == pdList.size()) {
// Try and find the best match using the property types
List<PropertyDescriptorPlus> list =
new ArrayList<PropertyDescriptorPlus>(pdList);
List<PropertyDescriptorPlus> args = new ArrayList<PropertyDescriptorPlus>();
Class[] parms = tryConstructor.getParameterTypes();
boolean valid = true;
// Assume the message is first in the constructor
for (int j = 0; j < list.size(); j++) {
if ("message".equals(list.get(j).getPropertyName())) {
args.add(list.remove(j));
}
}
if (args.size() != 1 ||
!parms[0].isAssignableFrom(args.get(0).getPropertyType())) {
valid = false;
}
// Now process the rest of the args
for (int j = 1; j < parms.length && valid; j++) {
// Find a compatible argument
Class parm = parms[j];
boolean found = false;
for (int k = 0; k < list.size() && !found; k++) {
Class arg = list.get(k).getPropertyType();
if (parm.isAssignableFrom(arg)) {
found = true;
args.add(list.remove(k));
}
}
// If no compatible argument then this constructor is not valid
if (!found) {
valid = false;
}
}
// A constructor is found
if (valid) {
constructor = tryConstructor;
for (int index = 0; index < args.size(); index++) {
childNames.add(args.get(index).getPropertyName());
}
}
}
}
}
return constructor;
}
/**
* Get the child objects map that is required by the wrapper tool.
*
* @param t Exception
* @param ParameterDescriptorPlus map for Throwable t
* @return Map with key is bean property names and values are objects from the Exception
* @throws IntrospectionException
*/
private static Map<String, Object> getChildObjectsMap(Throwable t,
Map<String, PropertyDescriptorPlus> pdMap)
throws IntrospectionException, InvocationTargetException, IllegalAccessException {
Map<String, Object> coMap = new HashMap<String, Object>();
Iterator<Entry<String, PropertyDescriptorPlus>> it = pdMap.entrySet().iterator();
while (it.hasNext()) {
Entry<String, PropertyDescriptorPlus> entry = it.next();
String propertyName = entry.getValue().getPropertyName();
String xmlName = entry.getKey();
// Some propertyNames should be ignored.
if (!ignore.contains(propertyName)) {
Object value = entry.getValue().get(t);
coMap.put(xmlName, value);
}
}
return coMap;
}
}