blob: a866c5b8e1d44d7296a8b8042668e7a759249dda [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.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;
}
}
}