/* * 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 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.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 ); Object result = invoke(chain, promotedArgs, source); // Returned Holder data <T> are placed back in Holder<T>. boolean holderPattern = false; Class [] parameters = method.getParameterTypes(); if ( parameters != null ) { for ( int i = 0, resultIdx = 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[] resultArray = (Object[])result; holder.value = resultArray[++resultIdx]; } } } if ( holderPattern ) 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.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; } /** * 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 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; } }