blob: a48e6e2c2800123e567720f47f58134f2051ae38 [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.core.invocation.impl;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import javax.xml.ws.Holder;
import org.apache.tuscany.sca.assembly.Endpoint;
import org.apache.tuscany.sca.context.ThreadMessageContext;
import org.apache.tuscany.sca.core.context.ServiceReferenceExt;
import org.apache.tuscany.sca.interfacedef.DataType;
import org.apache.tuscany.sca.interfacedef.Operation;
import org.apache.tuscany.sca.interfacedef.ParameterMode;
import org.apache.tuscany.sca.interfacedef.java.JavaOperation;
import org.apache.tuscany.sca.invocation.InvocationChain;
import org.apache.tuscany.sca.invocation.Invoker;
import org.apache.tuscany.sca.invocation.Message;
import org.apache.tuscany.sca.invocation.MessageFactory;
import org.apache.tuscany.sca.runtime.Invocable;
import org.apache.tuscany.sca.runtime.RuntimeEndpoint;
import org.apache.tuscany.sca.runtime.RuntimeEndpointReference;
import org.oasisopen.sca.ServiceReference;
import org.oasisopen.sca.ServiceRuntimeException;
/**
* @version $Rev$ $Date$
*/
public class JDKInvocationHandler implements InvocationHandler, Serializable {
private static final long serialVersionUID = -3366410500152201371L;
protected MessageFactory messageFactory;
protected Endpoint target;
protected Invocable source;
protected ServiceReferenceExt<?> callableReference;
protected Class<?> businessInterface;
protected boolean fixedWire = true;
protected transient Map<Method, InvocationChain> chains = new IdentityHashMap<Method, InvocationChain>();
public JDKInvocationHandler(MessageFactory messageFactory, Class<?> businessInterface, Invocable source) {
this.messageFactory = messageFactory;
this.source = source;
this.businessInterface = businessInterface;
}
public JDKInvocationHandler(MessageFactory messageFactory, ServiceReference<?> callableReference) {
this.messageFactory = messageFactory;
this.callableReference = (ServiceReferenceExt<?>)callableReference;
if (callableReference != null) {
this.businessInterface = callableReference.getBusinessInterface();
this.source = (RuntimeEndpointReference) this.callableReference.getEndpointReference();
}
}
public Class<?> getBusinessInterface() {
return businessInterface;
}
protected Object getCallbackID() {
return null;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class == method.getDeclaringClass()) {
return invokeObjectMethod(method, args);
}
if (source == null) {
throw new ServiceRuntimeException("No runtime source is available");
}
if (source instanceof RuntimeEndpointReference) {
RuntimeEndpointReference epr = (RuntimeEndpointReference)source;
if (epr.isOutOfDate()) {
epr.rebuild();
chains.clear();
}
}
InvocationChain chain = getInvocationChain(method, source);
if (chain == null) {
throw new IllegalArgumentException("No matching operation is found: " + method);
}
// Holder pattern. Items stored in a Holder<T> are promoted to T.
// After the invoke, the returned data <T> are placed back in Holder<T>.
Object [] promotedArgs = promoteHolderArgs( args );
// Strip out OUT-only arguments. Not too sure if the presence
// of a sourceOperation is exactly the right check to use to
// know whether or not to do this, but will assume it is until
// learning otherwise.
Operation sourceOp = chain.getSourceOperation();
if (sourceOp != null) {
promotedArgs = removeOutOnlyArgs(sourceOp, promotedArgs );
}
Object result = invoke(method, chain, promotedArgs, source);
// TODO - Based on the code in JavaInterfaceIntrospectorImpl, it seems there are
// some cases involving generics that we're not taking into account.
boolean voidReturnType = (void.class == method.getReturnType() ? true : false);
// Returned Holder data <T> are placed back in Holder<T>.
boolean holderPattern = false;
Class [] parameters = method.getParameterTypes();
if ( parameters != null ) {
int resultIdx = (voidReturnType ? 0 : 1);
for ( int i = 0; i < parameters.length; i++ ) {
Class parameterType = parameters[ i ];
if ( isHolder( parameterType ) ) {
holderPattern = true;
// Pop results and place in holder (demote).
Holder holder = (Holder) args[ i ];
Object[] results = (Object[])result;
if ( result != null ) {
holder.value = results[resultIdx++];
}
}
}
}
if (holderPattern && result != null) {
if (voidReturnType) {
return null;
} else {
return ((Object[])result)[0];
}
} else {
return result;
}
}
/**
* Handle the methods on the Object.class
* @param method
* @param args
*/
protected Object invokeObjectMethod(Method method, Object[] args) throws Throwable {
String name = method.getName();
if ("toString".equals(name)) {
return "[Proxy - " + toString() + "]";
} else if ("equals".equals(name)) {
Object obj = args[0];
if (obj == null) {
return false;
}
if (!Proxy.isProxyClass(obj.getClass())) {
return false;
}
return equals(Proxy.getInvocationHandler(obj));
} else if ("hashCode".equals(name)) {
return hashCode();
} else {
return method.invoke(this);
}
}
/**
* Determines if the given operation matches the given method
*
* @return true if the operation matches, false if does not
*/
// FIXME: Should it be in the InterfaceContractMapper?
@SuppressWarnings("unchecked")
private static boolean match(Operation operation, Method method) {
if (operation instanceof JavaOperation) {
JavaOperation javaOp = (JavaOperation)operation;
Method m = javaOp.getJavaMethod();
if (!method.getName().equals(m.getName())) {
return false;
}
if (method.equals(m)) {
return true;
}
} else {
if (!method.getName().equals(operation.getName())) {
return false;
}
}
// For remotable interface, operation is not overloaded.
if (operation.getInterface().isRemotable()) {
return true;
}
Class<?>[] params = method.getParameterTypes();
DataType<List<DataType>> inputType = null;
if (operation.isInputWrapperStyle()) {
inputType = operation.getInputWrapper().getUnwrappedType();
} else {
inputType = operation.getInputType();
}
List<DataType> types = inputType.getLogical();
boolean matched = true;
if (types.size() == params.length && method.getName().equals(operation.getName())) {
for (int i = 0; i < params.length; i++) {
Class<?> clazz = params[i];
Class<?> type = types.get(i).getPhysical();
// Object.class.isAssignableFrom(int.class) returns false
if (type != Object.class && (!type.isAssignableFrom(clazz))) {
matched = false;
}
}
} else {
matched = false;
}
return matched;
}
protected synchronized InvocationChain getInvocationChain(Method method, Invocable source) {
if (source instanceof RuntimeEndpoint) {
// [rfeng] Start with the binding invocation chain
return source.getBindingInvocationChain();
}
if (fixedWire && chains.containsKey(method)) {
return chains.get(method);
}
InvocationChain found = null;
for (InvocationChain chain : source.getInvocationChains()) {
Operation operation = chain.getSourceOperation();
if (operation.isDynamic()) {
operation.setName(method.getName());
found = chain;
break;
} else if (match(operation, method)) {
found = chain;
break;
}
}
if (fixedWire) {
chains.put(method, found);
}
return found;
}
protected synchronized InvocationChain getInvocationChain(String opName, Invocable source) {
if (source instanceof RuntimeEndpoint) {
// [rfeng] Start with the binding invocation chain
return source.getBindingInvocationChain();
}
InvocationChain found = null;
for (InvocationChain chain : source.getInvocationChains()) {
Operation operation = chain.getSourceOperation();
if (operation.isDynamic()) {
operation.setName(opName);
found = chain;
break;
} else if (operation.getName().equals(opName)) {
found = chain;
break;
}
}
return found;
}
protected void setEndpoint(Endpoint endpoint) {
this.target = endpoint;
}
protected Object invoke(Method method, InvocationChain chain, Object[] args, Invocable source)
throws Throwable {
return invoke( method, chain, args, source, null );
}
/**
* Invoke the chain
* @param chain - the chain
* @param args - arguments to the invocation as an array of Objects
* @param source - the Endpoint or EndpointReference to which the chain relates
* @param msgID - an ID for the message being sent, may be null
* @return - the Response message from the invocation
* @throws Throwable - if any exception occurs during the invocation
*/
protected Object invoke(Method method, InvocationChain chain, Object[] args, Invocable source, String msgID)
throws Throwable {
Message msg = messageFactory.createMessage();
if (source instanceof RuntimeEndpointReference) {
msg.setFrom((RuntimeEndpointReference)source);
}
if (target != null) {
msg.setTo(target);
} else {
if (source instanceof RuntimeEndpointReference) {
msg.setTo(((RuntimeEndpointReference)source).getTargetEndpoint());
}
}
Invoker headInvoker = chain.getHeadInvoker();
Operation operation = null;
if(source instanceof RuntimeEndpoint) {
// [rfeng] We cannot use the targetOperation from the binding invocation chain.
// For each method, we need to find the matching operation so that we can set the operation on to the message
for (InvocationChain c : source.getInvocationChains()) {
Operation op = c.getTargetOperation();
if (method.getName().equals(op.getName())) {
operation = op;
break;
}
}
} else {
operation = chain.getTargetOperation();
}
msg.setOperation(operation);
msg.setBody(args);
Message msgContext = ThreadMessageContext.getMessageContext();
// Deal with header information that needs to be copied from the message context to the new message...
transferMessageHeaders( msg, msgContext);
ThreadMessageContext.setMessageContext(msg);
// If there is a supplied message ID, place its value into the Message Header under "MESSAGE_ID"
if( msgID != null ){
msg.getHeaders().put("MESSAGE_ID", msgID);
} // end if
try {
// dispatch the source down the chain and get the response
Message resp = headInvoker.invoke(msg);
Object body = resp.getBody();
if (resp.isFault()) {
throw (Throwable)body;
}
return body;
} finally {
ThreadMessageContext.setMessageContext(msgContext);
}
}
/**
* Transfer relevant header information from the old message (incoming) to the new message (outgoing)
* @param newMsg
* @param oldMsg
*/
protected void transferMessageHeaders( Message newMsg, Message oldMsg ) {
if( oldMsg == null ) return;
// For the present, simply copy all the headers
if( !oldMsg.getHeaders().isEmpty() ) newMsg.getHeaders().putAll( oldMsg.getHeaders() );
} // end transferMessageHeaders
/**
* @return the callableReference
*/
public ServiceReference<?> getCallableReference() {
return callableReference;
}
/**
* @param callableReference the callableReference to set
*/
public void setCallableReference(ServiceReference<?> callableReference) {
this.callableReference = (ServiceReferenceExt<?>)callableReference;
}
/**
* Creates a copy of arguments. Holder<T> values are promoted to T.
* Note. It is essential that arg Holders not be destroyed here.
* PromotedArgs should not destroy holders. They are used on response return.
* @param args containing Holders and other objects.
* @return Object []
*/
protected static Object [] promoteHolderArgs( Object [] args ) {
if ( args == null )
return args;
Object [] promotedArgs = new Object[ args.length ];
for ( int i = 0; i < args.length; i++ ) {
Object argument = args[ i ];
if ( argument != null ) {
if ( isHolder( argument ) ) {
promotedArgs[ i ] = ((Holder)argument).value;
} else {
promotedArgs[ i ] = args[ i ];
}
}
}
return promotedArgs;
}
/**
* Given an argument array, filters out (removes) OUT-only parameters
* @param sourceOp
* @return array of filtered arguments
*/
Object[] removeOutOnlyArgs(Operation sourceOp, Object[] args) {
if ( args == null )
return args;
ArrayList<Object> retValList = new ArrayList<Object>();
List<ParameterMode> parmList = sourceOp.getParameterModes();
for (int i = 0; i < args.length; i++) {
if (parmList.get(i) != ParameterMode.OUT) {
retValList.add(args[i]);
}
}
return retValList.toArray();
}
/**
* Given a Class, tells if it is a Holder by comparing to "javax.xml.ws.Holder"
* @param testClass
* @return boolean whether class is Holder type.
*/
protected static boolean isHolder( Class testClass ) {
if ( testClass.getName().startsWith( "javax.xml.ws.Holder" )) {
return true;
}
return false;
}
/**
* Given an Object, tells if it is a Holder by comparing to "javax.xml.ws.Holder"
* @param testClass
* @return boolean stating whether Object is a Holder type.
* @author DOB
*/
protected static boolean isHolder( Object object ) {
String objectName = object.getClass().getName();
if ( object instanceof javax.xml.ws.Holder ) {
return true;
}
return false;
}
protected Object invoke(String opName, Object args, Invocable source, String msgID) throws Throwable {
if (source instanceof RuntimeEndpointReference) {
RuntimeEndpointReference epr = (RuntimeEndpointReference)source;
if (epr.isOutOfDate()) {
epr.rebuild();
chains.clear();
}
}
InvocationChain chain = getInvocationChain(opName, source);
if (chain == null) {
throw new IllegalArgumentException("No matching operation is found: " + opName);
}
Message msg = messageFactory.createMessage();
if (source instanceof RuntimeEndpointReference) {
msg.setFrom((RuntimeEndpointReference)source);
}
if (target != null) {
msg.setTo(target);
} else {
if (source instanceof RuntimeEndpointReference) {
msg.setTo(((RuntimeEndpointReference)source).getTargetEndpoint());
}
}
Invoker headInvoker = chain.getHeadInvoker();
Operation operation = null;
if (source instanceof RuntimeEndpoint) {
// [rfeng] We cannot use the targetOperation from the binding
// invocation chain.
// For each method, we need to find the matching operation so that
// we can set the operation on to the message
for (InvocationChain c : source.getInvocationChains()) {
Operation op = c.getTargetOperation();
if (opName.equals(op.getName())) {
operation = op;
break;
}
}
} else {
operation = chain.getTargetOperation();
}
msg.setOperation(operation);
msg.setBody(args);
Message msgContext = ThreadMessageContext.getMessageContext();
// Deal with header information that needs to be copied from the message
// context to the new message...
transferMessageHeaders(msg, msgContext);
ThreadMessageContext.setMessageContext(msg);
// If there is a supplied message ID, place its value into the Message
// Header under "MESSAGE_ID"
if (msgID != null) {
msg.getHeaders().put("MESSAGE_ID", msgID);
} // end if
try {
// dispatch the source down the chain and get the response
Message resp = headInvoker.invoke(msg);
Object body = resp.getBody();
if (resp.isFault()) {
throw (Throwable)body;
}
return body;
} finally {
ThreadMessageContext.setMessageContext(msgContext);
}
}
public Invocable getSource() {
return source;
}
}