blob: d0095f45ee229f459ce9add13f3aab43c5e49a07 [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.IdentityHashMap;
import java.util.List;
import java.util.Map;
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.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);
}
// send the invocation down the source
Object result = invoke(chain, args, source);
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.isWrapperStyle()) {
inputType = operation.getWrapper().getUnwrappedInputType();
} 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) {
InvocationChain invocationChain = source.getBindingInvocationChain();
for (InvocationChain chain : source.getInvocationChains()) {
Operation operation = chain.getTargetOperation();
if (method.getName().equals(operation.getName())) {
invocationChain.setTargetOperation(operation);
}
}
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 void setEndpoint(Endpoint endpoint) {
this.target = endpoint;
}
protected Object invoke(InvocationChain chain, Object[] args, Invocable source)
throws Throwable {
return invoke( 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(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 = 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
*/
private 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;
}
}