/* * 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.tuscany.sca.databinding.impl; import static org.apache.tuscany.sca.databinding.DataBinding.IDL_FAULT; import static org.apache.tuscany.sca.databinding.DataBinding.IDL_OUTPUT; import java.io.Serializable; import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.UUID; import javax.xml.namespace.QName; import org.apache.tuscany.sca.core.ExtensionPointRegistry; import org.apache.tuscany.sca.core.UtilityExtensionPoint; import org.apache.tuscany.sca.databinding.DataBinding; import org.apache.tuscany.sca.databinding.DataBindingExtensionPoint; import org.apache.tuscany.sca.databinding.DataPipe; import org.apache.tuscany.sca.databinding.DataPipeTransformer; import org.apache.tuscany.sca.databinding.Mediator; import org.apache.tuscany.sca.databinding.PullTransformer; import org.apache.tuscany.sca.databinding.PushTransformer; import org.apache.tuscany.sca.databinding.TransformationContext; import org.apache.tuscany.sca.databinding.TransformationException; import org.apache.tuscany.sca.databinding.Transformer; import org.apache.tuscany.sca.databinding.TransformerExtensionPoint; import org.apache.tuscany.sca.databinding.javabeans.JavaBeansDataBinding; import org.apache.tuscany.sca.interfacedef.DataType; import org.apache.tuscany.sca.interfacedef.FaultExceptionMapper; import org.apache.tuscany.sca.interfacedef.InterfaceContractMapper; import org.apache.tuscany.sca.interfacedef.Operation; import org.apache.tuscany.sca.interfacedef.impl.DataTypeImpl; import org.apache.tuscany.sca.interfacedef.util.FaultException; import org.apache.tuscany.sca.interfacedef.util.XMLType; import org.oasisopen.sca.ServiceRuntimeException; /** * Default Mediator implementation * * @version $Rev$ $Date$ * @tuscany.spi.extension.asclient */ public class MediatorImpl implements Mediator { private static final String TARGET_OPERATION = "target.operation"; private static final String SOURCE_OPERATION = "source.operation"; private ExtensionPointRegistry registry; private DataBindingExtensionPoint dataBindings; private TransformerExtensionPoint transformers; private InterfaceContractMapper interfaceContractMapper; private FaultExceptionMapper faultExceptionMapper; MediatorImpl(DataBindingExtensionPoint dataBindings, TransformerExtensionPoint transformers) { this.dataBindings = dataBindings; this.transformers = transformers; } public MediatorImpl(ExtensionPointRegistry registry) { this.registry = registry; this.dataBindings = registry.getExtensionPoint(DataBindingExtensionPoint.class); this.transformers = registry.getExtensionPoint(TransformerExtensionPoint.class); UtilityExtensionPoint utilities = registry.getExtensionPoint(UtilityExtensionPoint.class); this.interfaceContractMapper = utilities.getUtility(InterfaceContractMapper.class); this.faultExceptionMapper = utilities.getUtility(FaultExceptionMapper.class); } @SuppressWarnings("unchecked") public Object mediate(Object source, DataType sourceDataType, DataType targetDataType, Map<String, Object> metadata) { if (sourceDataType == null || sourceDataType.getDataBinding() == null) { if (source != null) { Operation operation = (Operation)metadata.get(SOURCE_OPERATION); sourceDataType = dataBindings.introspectType(source, operation); } } if (sourceDataType == null || targetDataType == null) { return source; } else if (sourceDataType.equals(targetDataType)) { return source; } List<Transformer> path = getTransformerChain(sourceDataType, targetDataType); Object result = source; int size = path.size(); int i = 0; while (i < size) { Transformer transformer = path.get(i); TransformationContext context = createTransformationContext(sourceDataType, targetDataType, size, i, transformer, metadata); // the source and target type if (transformer instanceof PullTransformer) { // For intermediate node, set data type to null result = ((PullTransformer)transformer).transform(result, context); } else if (transformer instanceof PushTransformer) { DataPipeTransformer dataPipeFactory = (i < size - 1) ? (DataPipeTransformer)path.get(++i) : null; DataPipe dataPipe = dataPipeFactory == null ? null : dataPipeFactory.newInstance(); ((PushTransformer)transformer).transform(result, dataPipe.getSink(), context); result = dataPipe.getResult(); } i++; } return result; } private TransformationContext createTransformationContext(DataType sourceDataType, DataType targetDataType, int size, int index, Transformer transformer, Map<String, Object> metadata) { DataType sourceType = (index == 0) ? sourceDataType : new DataTypeImpl<Object>(transformer.getSourceDataBinding(), Object.class, sourceDataType.getLogical()); DataType targetType = (index == size - 1) ? targetDataType : new DataTypeImpl<Object>(transformer.getTargetDataBinding(), Object.class, targetDataType.getLogical()); Map<String, Object> copy = new HashMap<String, Object>(); if (metadata != null) { copy.putAll(metadata); } copy.put(ExtensionPointRegistry.class.getName(), registry); TransformationContext context = new TransformationContextImpl(sourceType, targetType, copy); return context; } @SuppressWarnings("unchecked") public void mediate(Object source, Object target, DataType sourceDataType, DataType targetDataType, Map<String, Object> metadata) { if (source == null) { // Shortcut for null value return; } if (sourceDataType == null || sourceDataType.getDataBinding() == null) { Operation operation = (Operation)metadata.get(SOURCE_OPERATION); sourceDataType = dataBindings.introspectType(source, operation); } if (sourceDataType == null) { return; } else if (sourceDataType.equals(targetDataType)) { return; } List<Transformer> path = getTransformerChain(sourceDataType, targetDataType); Object result = source; int size = path.size(); for (int i = 0; i < size; i++) { Transformer transformer = path.get(i); TransformationContext context = createTransformationContext(sourceDataType, targetDataType, size, i, transformer, metadata); if (transformer instanceof PullTransformer) { result = ((PullTransformer)transformer).transform(result, context); } else if (transformer instanceof PushTransformer) { DataPipeTransformer dataPipeFactory = (i < size - 1) ? (DataPipeTransformer)path.get(++i) : null; DataPipe dataPipe = dataPipeFactory == null ? null : dataPipeFactory.newInstance(); Object sink = dataPipe != null ? dataPipe.getSink() : target; ((PushTransformer)transformer).transform(result, sink, context); result = (dataPipe != null) ? dataPipe.getResult() : null; } } } private List<Transformer> getTransformerChain(DataType sourceDataType, DataType targetDataType) { String sourceId = sourceDataType.getDataBinding(); String targetId = targetDataType.getDataBinding(); List<Transformer> path = transformers.getTransformerChain(sourceId, targetId); if (path == null) { TransformationException ex = new TransformationException("No path found for the transformation: " + sourceId + "->" + targetId); ex.setSourceDataBinding(sourceId); ex.setTargetDataBinding(targetId); throw ex; } return path; } public DataBindingExtensionPoint getDataBindings() { return dataBindings; } public TransformerExtensionPoint getTransformers() { return transformers; } /** * Find the fault data type behind the exception data type * @param exceptionType The exception data type * @return The fault data type */ private DataType getFaultType(DataType exceptionType) { return exceptionType == null ? null : (DataType)exceptionType.getLogical(); } /** * @param qn1 * @param qn2 */ private boolean matches(QName qn1, QName qn2) { if (qn1 == qn2) { return true; } if (qn1 == null || qn2 == null) { return false; } String ns1 = qn1.getNamespaceURI(); String ns2 = qn2.getNamespaceURI(); String e1 = qn1.getLocalPart(); String e2 = qn2.getLocalPart(); if (e1.equals(e2) && (ns1.equals(ns2) || ns1.equals(ns2 + "/") || ns2.equals(ns1 + "/"))) { // Tolerating the trailing / which is required by JAX-WS java package --> xml ns mapping return true; } return false; } /** * @param source The source exception * @param sourceExType The data type for the source exception * @param targetExType The data type for the target exception * @param sourceType The fault type for the source * @param targetType The fault type for the target * @return */ private Object transformException(Object source, DataType sourceExType, DataType targetExType, DataType sourceType, DataType targetType, Map<String, Object> metadata) { if (sourceType == targetType || (sourceType != null && sourceType.equals(targetType))) { return source; } DataType<DataType> eSourceDataType = new DataTypeImpl<DataType>(IDL_FAULT, sourceExType.getPhysical(), sourceType); DataType<DataType> eTargetDataType = new DataTypeImpl<DataType>(IDL_FAULT, targetExType.getPhysical(), targetType); return mediate(source, eSourceDataType, eTargetDataType, metadata); } // // Assumes we're going from target->source, knowing that we're throwing BACK an exception, rather than the more // obvious source->target // public Object mediateFault(Object result, Operation sourceOperation, Operation targetOperation, Map<String, Object> metadata) { // FIXME: How to match fault data to a fault type for the // operation? // If the result is from an InvocationTargetException look at // the actual cause. if (result instanceof InvocationTargetException) { result = ((InvocationTargetException)result).getCause(); } DataType targetDataType = findFaultDataType(targetOperation, result); DataType targetFaultType = getFaultType(targetDataType); if (targetFaultType == null) { // No matching fault type, it's a system exception Throwable cause = (Throwable)result; throw new ServiceRuntimeException(cause); } // FIXME: How to match a source fault type to a target fault // type? DataType sourceDataType = null; DataType sourceFaultType = null; for (DataType exType : sourceOperation.getFaultTypes()) { DataType faultType = getFaultType(exType); // Match by the QName (XSD element) of the fault type if (faultType != null && typesMatch(targetFaultType.getLogical(), faultType.getLogical())) { sourceDataType = exType; sourceFaultType = faultType; break; } } if (sourceFaultType == null) { // No matching fault type, it's a system exception Throwable cause = (Throwable)result; throw new ServiceRuntimeException(cause); } Map<String, Object> context = new HashMap<String, Object>(); if (metadata != null) { context.putAll(metadata); } if (targetOperation != null) { context.put(SOURCE_OPERATION, targetOperation); } if (sourceOperation != null) { context.put(TARGET_OPERATION, sourceOperation); } Object newResult = transformException(result, targetDataType, sourceDataType, targetFaultType, sourceFaultType, context); return newResult; } /** * Look up the fault data type that matches the fault or exception instance * @param operation The operation * @param faultOrException The fault or exception * @return The matching fault data type */ private DataType findFaultDataType(Operation operation, Object faultOrException) { DataType targetDataType = null; for (DataType exType : operation.getFaultTypes()) { if (((Class)exType.getPhysical()).isInstance(faultOrException)) { if (faultOrException instanceof FaultException) { DataType faultType = (DataType)exType.getLogical(); if (((FaultException)faultOrException).isMatchingType(faultType.getLogical())) { targetDataType = exType; break; } } else { targetDataType = exType; break; } } } return targetDataType; } private boolean typesMatch(Object first, Object second) { if (first.equals(second)) { return true; } if (first instanceof XMLType && second instanceof XMLType) { XMLType t1 = (XMLType)first; XMLType t2 = (XMLType)second; // TUSCANY-2113, we should compare element names only return matches(t1.getElementName(), t2.getElementName()); } return false; } /** * Assumes we're going from target-to-source, knowing that we're sending BACK an output response, rather than the more * obvious source-to-target. * * @param output * @param sourceOperation * @param targetOperation * @return */ public Object mediateOutput(Object output, Operation sourceOperation, Operation targetOperation, Map<String, Object> metadata) { // Create a data type to represent the ouput produced by the target operation DataType<DataType> targetType = new DataTypeImpl<DataType>(IDL_OUTPUT, Object.class, targetOperation.getOutputType()); // Create a data type to represent the ouput expected by the source operation DataType<DataType> sourceType = new DataTypeImpl<DataType>(IDL_OUTPUT, Object.class, sourceOperation.getOutputType()); if (sourceType == targetType || (sourceType != null && sourceType.equals(targetType))) { return output; } Map<String, Object> context = new HashMap<String, Object>(); if (metadata != null) { context.putAll(metadata); } if (targetOperation != null) { context.put(SOURCE_OPERATION, targetOperation); } if (sourceOperation != null) { context.put(TARGET_OPERATION, sourceOperation); } return mediate(output, targetType, sourceType, context); } public Object mediateInput(Object input, Operation sourceOperation, Operation targetOperation, Map<String, Object> metadata) { // Get the data type to represent the input passed in by the source operation DataType sourceType = sourceOperation.getInputType(); // Get the data type to represent the input expected by the target operation DataType targetType = targetOperation.getInputType(); if (sourceType == targetType || (sourceType != null && sourceType.equals(targetType))) { return input; } Map<String, Object> context = new HashMap<String, Object>(); if (metadata != null) { context.putAll(metadata); } if (sourceOperation != null) { context.put(SOURCE_OPERATION, sourceOperation); } if (targetOperation != null) { context.put(TARGET_OPERATION, targetOperation); } return mediate(input, sourceType, targetType, context); } public TransformationContext createTransformationContext() { return new TransformationContextImpl(); } public TransformationContext createTransformationContext(DataType sourceDataType, DataType targetDataType, Map<String, Object> metadata) { return new TransformationContextImpl(sourceDataType, targetDataType, metadata); } public Object copy(Object data, DataType dataType) { return copy(data, dataType, dataType, null, null); } public Object copy(Object data, DataType sourceDataType, DataType targetDataType) { return copy(data, sourceDataType, targetDataType, null, null); } /** * Copy data using the specified databinding. * @param data input data * @param sourceDataType * @return a copy of the data */ public Object copy(Object data, DataType sourceDataType, DataType targetDataType, Operation sourceOperation, Operation targetOperation) { if (data == null) { return null; } Class<?> clazz = data.getClass(); if (String.class == clazz || clazz.isPrimitive() || Number.class.isAssignableFrom(clazz) || Boolean.class.isAssignableFrom(clazz) || Character.class.isAssignableFrom(clazz) || Byte.class.isAssignableFrom(clazz) || URI.class == clazz || UUID.class == clazz || QName.class == clazz) { // Immutable classes return data; } DataBinding javaBeansDataBinding = dataBindings.getDataBinding(JavaBeansDataBinding.NAME); // FIXME: The JAXB databinding is hard-coded here DataBinding jaxbDataBinding = dataBindings.getDataBinding("javax.xml.bind.JAXBElement"); DataBinding dataBinding = dataBindings.getDataBinding(sourceDataType.getDataBinding()); // If no databinding was specified, introspect the given arg to // determine its databinding if (dataBinding == null) { if (!"java:array".equals(sourceDataType.getDataBinding())) { sourceDataType = dataBindings.introspectType(data, sourceOperation); if (sourceDataType != null) { String db = sourceDataType.getDataBinding(); dataBinding = dataBindings.getDataBinding(db); if (dataBinding == null && db != null) { return data; } } } if (dataBinding == null) { // Default to the JavaBean databinding dataBinding = dataBindings.getDataBinding(JavaBeansDataBinding.NAME); } } // Use the JAXB databinding to copy non-Serializable data if (dataBinding == javaBeansDataBinding) { // If the input data is an array containing non Serializable elements // use JAXB clazz = data.getClass(); if (clazz.isArray()) { if (Array.getLength(data) != 0) { Object element = Array.get(data, 0); if (element != null && !(element instanceof Serializable)) { dataBinding = jaxbDataBinding; } } } else { // If the input data is not Serializable use JAXB if (!((data instanceof Serializable) || (data instanceof Cloneable))) { dataBinding = jaxbDataBinding; } } } if (dataBinding == null) { return data; } return dataBinding.copy(data, sourceDataType, targetDataType, sourceOperation, targetOperation); } /** * Copy an array of data objects passed to an operation * @param data array of objects to copy * @return the copy */ public Object copyInput(Object input, Operation operation) { return copyInput(input, operation, operation); } public Object copyInput(Object input, Operation sourceOperation, Operation targetOperation) { if (input == null) { return null; } Object[] data = (input instanceof Object[]) ? (Object[])input : new Object[] {input}; List<DataType> inputTypes = sourceOperation.getInputType().getLogical(); List<DataType> inputTypesTarget = targetOperation == null ? null : targetOperation.getInputType().getLogical(); Object[] copy = new Object[data.length]; Map<Object, Object> map = new IdentityHashMap<Object, Object>(); for (int i = 0, size = inputTypes.size(); i < size; i++) { Object arg = data[i]; if (arg == null) { copy[i] = null; } else { Object copiedArg = map.get(arg); if (copiedArg != null) { copy[i] = copiedArg; } else { copiedArg = copy(arg, inputTypes.get(i), inputTypesTarget == null ? null : inputTypesTarget.get(i), sourceOperation, targetOperation); map.put(arg, copiedArg); copy[i] = copiedArg; } } } return copy; } public Object copyOutput(Object data, Operation operation) { return copyOutput(data, operation, operation); } public Object copyOutput(Object data, Operation sourceOperation, Operation targetOperation) { return copy(data, targetOperation.getOutputType(), sourceOperation.getOutputType(), targetOperation, sourceOperation); } public Object copyFault(Object fault, Operation operation) { return copyFault(fault, operation, operation); } public Object copyFault(Object fault, Operation sourceOperation, Operation targetOperation) { if (faultExceptionMapper == null) { return fault; } List<DataType> fts = targetOperation.getFaultTypes(); for (int i = 0; i < fts.size(); i++) { DataType et = fts.get(i); if (et.getPhysical().isInstance(fault)) { Throwable ex = (Throwable)fault; DataType<DataType> exType = findFaultDataType(targetOperation, fault); DataType faultType = getFaultType(exType); Object faultInfo = faultExceptionMapper.getFaultInfo(ex, faultType.getPhysical(), targetOperation); DataType targetExType = findSourceFaultDataType(sourceOperation, exType); DataType targetFaultType = getFaultType(targetExType); faultInfo = copy(faultInfo, faultType, targetFaultType, targetOperation, sourceOperation); fault = faultExceptionMapper.wrapFaultInfo(targetExType, ex.getMessage(), faultInfo, ex.getCause(), sourceOperation); return fault; } } return fault; } /** * Lookup a fault data type from the source operation which matches the target fault data type * @param sourceOperation The source operation * @param targetExceptionType The target fault data type * @return The matching source target fault type */ private DataType findSourceFaultDataType(Operation sourceOperation, DataType targetExceptionType) { boolean remotable = sourceOperation.getInterface().isRemotable(); DataType targetFaultType = getFaultType(targetExceptionType); for (DataType dt : sourceOperation.getFaultTypes()) { DataType sourceFaultType = getFaultType(dt); if (interfaceContractMapper.isCompatible(targetFaultType, sourceFaultType, remotable)) { return dt; } } return null; } }