/* * 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.interfacedef.java.jaxws; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Future; import javax.xml.ws.AsyncHandler; import javax.xml.ws.Response; import org.apache.tuscany.sca.core.ExtensionPointRegistry; import org.apache.tuscany.sca.interfacedef.DataType; import org.apache.tuscany.sca.interfacedef.InvalidInterfaceException; import org.apache.tuscany.sca.interfacedef.Operation; import org.apache.tuscany.sca.interfacedef.java.JavaInterface; import org.apache.tuscany.sca.interfacedef.java.JavaOperation; import org.apache.tuscany.sca.interfacedef.java.introspect.JavaInterfaceVisitor; public class JAXWSAsyncInterfaceProcessor implements JavaInterfaceVisitor { private static String ASYNC = "Async"; public JAXWSAsyncInterfaceProcessor(ExtensionPointRegistry registry) { } public void visitInterface(JavaInterface javaInterface) throws InvalidInterfaceException { List<Operation> validOperations = new ArrayList<Operation>(); List<Operation> asyncOperations = new ArrayList<Operation>(); validOperations.addAll(javaInterface.getOperations()); for (Operation o : javaInterface.getOperations()) { if (!o.getName().endsWith(ASYNC)) { JavaOperation op = (JavaOperation)o; if (op.getJavaMethod().getName().endsWith(ASYNC)) { continue; } for (Operation asyncOp : getAsyncOperations(javaInterface.getOperations(), op)) { if (isJAXWSAsyncPoolingOperation(op, asyncOp) || isJAXWSAsyncCallbackOperation(op, asyncOp)) { validOperations.remove(asyncOp); asyncOperations.add(asyncOp); } } } } javaInterface.getOperations().clear(); javaInterface.getOperations().addAll(validOperations); javaInterface.getAttributes().put("JAXWS-ASYNC-OPERATIONS", asyncOperations); } /** * The additional client-side asynchronous polling and callback methods defined by JAX-WS are recognized in a Java interface as follows: * For each method M in the interface, if another method P in the interface has * * a) a method name that is M's method name with the characters "Async" appended, and * b) the same parameter signature as M, and * c)a return type of Response<R> where R is the return type of M * * @param operation * @param asyncOperation * @return */ private static boolean isJAXWSAsyncPoolingOperation(Operation operation, Operation asyncOperation) { if (asyncOperation.getOutputType() == null || Response.class != asyncOperation.getOutputType().getPhysical()) { // The return type is not Response<T> return false; } //the same parameter signature as M List<DataType> operationInputType = operation.getInputType().getLogical(); List<DataType> asyncOperationInputType = asyncOperation.getInputType().getLogical(); int size = operationInputType.size(); if (asyncOperationInputType.size() != size) { return false; } for (int i = 0; i < size; i++) { if (!isCompatible(operationInputType.get(i), asyncOperationInputType.get(i))) { return false; } } //a return type of Response<R> where R is the return type of M DataType<?> operationOutputType = operation.getOutputType(); DataType<?> asyncOperationOutputType = asyncOperation.getOutputType(); if (operationOutputType != null && asyncOperationOutputType != null) { Class<?> asyncReturnTypeClass = (Class<?>)asyncOperationOutputType.getPhysical(); if (asyncReturnTypeClass == Response.class) { //now check the actual type of the Response<R> with R Class<?> returnType = operationOutputType.getPhysical(); Class<?> asyncActualReturnTypeClass = Object.class; if (asyncOperationOutputType.getGenericType() instanceof ParameterizedType) { ParameterizedType asyncReturnType = (ParameterizedType)asyncOperationOutputType.getGenericType(); asyncActualReturnTypeClass = (Class<?>)asyncReturnType.getActualTypeArguments()[0]; } if (operation.getWrapper() != null) { // The return type could be the wrapper type per JAX-WS spec Class<?> wrapperClass = operation.getWrapper().getOutputWrapperClass(); if (wrapperClass == asyncActualReturnTypeClass) { return true; } } if (returnType == asyncActualReturnTypeClass || returnType.isPrimitive() && primitiveAssignable(returnType, asyncActualReturnTypeClass)) { return true; } else { return false; } } } return true; } /** * For each method M in the interface, if another method C in the interface has * a) a method name that is M's method name with the characters "Async" appended, and * b) a parameter signature that is M's parameter signature with an additional * final parameter of type AsyncHandler<R> where R is the return type of M, and * c) a return type of Future<?> * * then C is a JAX-WS callback method that isn't part of the SCA interface contract. * * @param operation * @param asyncOperation * @return */ private static boolean isJAXWSAsyncCallbackOperation(Operation operation, Operation asyncOperation) { if (asyncOperation.getOutputType() == null || Future.class != asyncOperation.getOutputType().getPhysical()) { // The return type is not Future<?> return false; } //a parameter signature that is M's parameter signature //with an additional final parameter of type AsyncHandler<R> where R is the return type of M, and List<DataType> operationInputType = operation.getInputType().getLogical(); List<DataType> asyncOperationInputType = asyncOperation.getInputType().getLogical(); int size = operationInputType.size(); if (asyncOperationInputType.size() != size + 1) { return false; } for (int i = 0; i < size; i++) { if (!isCompatible(operationInputType.get(i), asyncOperationInputType.get(i))) { return false; } } Type genericParamType = asyncOperationInputType.get(size).getGenericType(); Class<?> asyncLastParameterTypeClass = asyncOperationInputType.get(size).getPhysical(); if (asyncLastParameterTypeClass == AsyncHandler.class) { //now check the actual type of the AsyncHandler<R> with R Class<?> returnType = operation.getOutputType().getPhysical(); Class<?> asyncActualLastParameterTypeClass = Object.class; if (genericParamType instanceof ParameterizedType) { ParameterizedType asyncLastParameterType = (ParameterizedType)genericParamType; asyncActualLastParameterTypeClass = (Class<?>)asyncLastParameterType.getActualTypeArguments()[0]; } if (operation.getWrapper() != null) { // The return type could be the wrapper type per JAX-WS spec Class<?> wrapperClass = operation.getWrapper().getOutputWrapperClass(); if (wrapperClass == asyncActualLastParameterTypeClass) { return true; } } if (returnType == asyncActualLastParameterTypeClass || returnType.isPrimitive() && primitiveAssignable(returnType, asyncActualLastParameterTypeClass)) { return true; } else { return false; } } return true; } /** * Get operation by name * * @param operations * @param operationName * @return */ private static List<Operation> getAsyncOperations(List<Operation> operations, JavaOperation op) { List<Operation> returnOperations = new ArrayList<Operation>(); for (Operation o : operations) { if (o == op) { continue; } String operationName = op.getName(); if (o.getName().equals(operationName)) { // Async operations have the same name when @WebMethod is present /* JavaOperation jop = (JavaOperation)o; if (op.getJavaMethod().getName().equals(jop.getJavaMethod().getName() + ASYNC)) { returnOperations.add(o); } */ returnOperations.add(o); } else if (o.getName().equals(operationName + ASYNC)) { returnOperations.add(o); } } return returnOperations; } /** * Check if two operation parameters are compatible * * @param source * @param target * @return */ private static boolean isCompatible(DataType<?> source, DataType<?> target) { if (source == target) { return true; } return target.getPhysical().isAssignableFrom(source.getPhysical()); } /** * Compares a two types, assuming one is a primitive, to determine if the * other is its object counterpart */ private static boolean primitiveAssignable(Class<?> memberType, Class<?> param) { if (memberType == Integer.class) { return param == Integer.TYPE; } else if (memberType == Double.class) { return param == Double.TYPE; } else if (memberType == Float.class) { return param == Float.TYPE; } else if (memberType == Short.class) { return param == Short.TYPE; } else if (memberType == Character.class) { return param == Character.TYPE; } else if (memberType == Boolean.class) { return param == Boolean.TYPE; } else if (memberType == Byte.class) { return param == Byte.TYPE; } else if (param == Integer.class) { return memberType == Integer.TYPE; } else if (param == Double.class) { return memberType == Double.TYPE; } else if (param == Float.class) { return memberType == Float.TYPE; } else if (param == Short.class) { return memberType == Short.TYPE; } else if (param == Character.class) { return memberType == Character.TYPE; } else if (param == Boolean.class) { return memberType == Boolean.TYPE; } else if (param == Byte.class) { return memberType == Byte.TYPE; } else { return false; } } }