View Javadoc

1   /* (c) Copyright 2003 Caleigo AB, All rights reserved. 
2    * 
3    * This library is free software; you can redistribute it and/or
4    * modify it under the terms of the GNU Lesser General Public
5    * License as published by the Free Software Foundation; either
6    * version 2.1 of the License, or (at your option) any later version.
7    * 
8    * This library is distributed in the hope that it will be useful,
9    * but WITHOUT ANY WARRANTY; without even the implied warranty of
10   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11   * Lesser General Public License for more details.
12   * 
13   * You should have received a copy of the GNU Lesser General Public
14   * License along with this library; if not, write to the Free Software
15   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16   *  
17   */
18  
19  package org.caleigo.toolkit.tunnel;
20  
21  
22  import java.lang.reflect.*;
23  import java.util.*;
24  
25  import org.caleigo.toolkit.log.*;
26  
27  /***
28   *
29   * @author  Mattias Hagstrand
30   * @version 1.00
31   * 
32   *//* 
33   *
34   * WHEN        WHO               WHY & WHAT
35   * -----------------------------------------------------------------------------
36   * 2002-07-12  Mattias Hagstrand    Creation
37   */
38  public class ProxyHandler implements IMessageConsumer
39  {
40      // Data members ------------------------------------------------------------
41      protected int mCurrentProxyID;
42      protected ITunnel mTunnel;
43      protected Hashtable mProxyObjects;
44      protected Hashtable mRemoteInvocationHandlers;
45      
46      // Constructors ------------------------------------------------------------
47      public ProxyHandler(ITunnel tunnel)
48      {
49          mTunnel = tunnel;
50          mProxyObjects = new Hashtable();
51          mRemoteInvocationHandlers = new Hashtable();
52          mTunnel.addMessageConsumer(this);
53      }
54      
55      // Action methods ----------------------------------------------------------
56      
57      /*** Creates a proxy for all interfaces that the provided object and its
58       * superclasses implement. Invocations on methods in the interfaces will be
59       * forwarded to the provided object.
60       */
61      public Object createProxy(Object obj)
62      {
63          if (!(obj instanceof IDistributable))
64              throw new IllegalArgumentException("Object must be an IDistributable");
65          
66          List proxyInterfaces = new ArrayList();
67          Class currentClass = obj.getClass();
68          while (currentClass != null)
69          {
70              Class[] interfaces = currentClass.getInterfaces();
71              for (int i = 0; i < interfaces.length; i++)
72                  proxyInterfaces.add(interfaces[i]);
73              currentClass = currentClass.getSuperclass();
74          }
75          Class[] proxyInterfaceClasses = (Class[]) proxyInterfaces.toArray(new Class[0]);
76          Log.print(this, "Creating proxy for interfaces:");
77          for (int i = 0; i < proxyInterfaceClasses.length; i++)
78              Log.print(this, proxyInterfaceClasses[i].getName());
79          int proxyID = this.generateProxyID();
80          Object proxy = Proxy.newProxyInstance(obj.getClass().getClassLoader(),
81                                                proxyInterfaceClasses,
82                                                new DynamicRemoteProxy(proxyID, this, proxyInterfaceClasses));
83          mProxyObjects.put(new Integer(proxyID), obj);
84          return proxy;
85      }
86      
87      /*** Creates a proxy for the provided interfaces.
88       *
89       * @param handler   object that will handle invocations on the created proxy.
90       *                  The handler is allowed to be <code>null</code> in which
91       *                  case invocation handlers must be registered for the
92       *                  interfaces.
93       */
94      public Object createProxy(Class[] interfaceClasses, Object handler)
95      {
96          if (interfaceClasses.length == 0)
97              throw new IllegalArgumentException("No interface classes found");
98          
99          Log.print(this, "Creating proxy for interfaces:");
100         for (int i = 0; i < interfaceClasses.length; i++)
101             Log.print(this, interfaceClasses[i].getName());
102         int proxyID = this.generateProxyID();
103         Object proxy = Proxy.newProxyInstance(interfaceClasses[0].getClassLoader(),
104                                               interfaceClasses,
105                                               new DynamicRemoteProxy(proxyID, this, interfaceClasses));
106         if (handler != null)
107             mProxyObjects.put(new Integer(proxyID), handler);
108         return proxy;
109     }
110     
111     public void finishProxy(Object proxy)
112     {
113         Log.print(this, "Proxy finished");
114         mProxyObjects.remove(new Integer(((DynamicRemoteProxy) Proxy.getInvocationHandler(proxy)).getID()));
115     }
116     
117     /*** Register an object that handles remote invokations for the provided
118      * interface.
119      */
120     public void registerRemoteInvocationHandler(Class interfaceClass, Object handler)
121     {
122         if (!interfaceClass.isInterface())
123             throw new IllegalArgumentException("The interface class must be an interface");
124         
125         if (!interfaceClass.isInstance(handler))
126             throw new IllegalArgumentException("Handler must be an instance of the interface class");
127         
128         mRemoteInvocationHandlers.put(interfaceClass.getName(), handler);
129     }
130     
131     // Access methods ----------------------------------------------------------
132     public ITunnel getTunnel()
133     {
134         return mTunnel;
135     }
136     
137     // IMessageConsumer implementation -----------------------------------------
138     
139     /***
140      * Returns <code>true</code> if this IMessageConsumer accepts the message.
141      * This method is allways called before <code>consumeMessage</code> for any
142      * given message.
143      */
144     public boolean acceptsMessage(Object message)
145     {
146         return (message instanceof DynamicRemoteProxy.IProxyInvocationMessage ||
147                 message instanceof DynamicRemoteProxy.IProxyCleanUpMessage);
148     }
149     
150     /***
151      * Tells the IMessageConsumer to consume a message.
152      */
153     public void consumeMessage(Object message)
154     {
155         Log.print(this, "Clean up message received");
156         mProxyObjects.remove(new Integer(((DynamicRemoteProxy.IProxyCleanUpMessage) message).getProxyID()));
157     }
158     
159     /***
160      * Tells the IMessageConsumer to consume a message and returns a new message
161      * that will be delivered to the sender of the originial message.
162      */
163     public Object answerMessage(Object message)
164     {
165         Log.print(this, "answerMessage");
166         
167         String[] interfaceClassNames = ((DynamicRemoteProxy.IProxyInvocationMessage) message).getInterfaceClassNames();
168         String methodName = ((DynamicRemoteProxy.IProxyInvocationMessage) message).getMethodName();
169         Method method = null;
170         Object target = null;
171         
172         // Get arguments and set the proxy handler to this for any DynamicRemoteProxy
173         Object[] arguments = ((DynamicRemoteProxy.IProxyInvocationMessage) message).getArguments();
174         if (arguments != null)
175             for (int i = 0; i < arguments.length; i++)
176                 if (arguments[i] != null && Proxy.isProxyClass(arguments[i].getClass()))
177                     ((DynamicRemoteProxy) Proxy.getInvocationHandler(arguments[i])).setProxyHandler(this);
178 
179         // Try to find a proxy object that should receive the invocation
180         target = mProxyObjects.get(new Integer(((DynamicRemoteProxy.IProxyInvocationMessage) message).getProxyID()));
181         if (target != null)
182             method = this.findMatchingMethod(target.getClass(), methodName, arguments);
183         
184         // Try to find a remote invocation handler that can handle the invocation
185         // if no proxy object was found
186         for (int i = 0; i < interfaceClassNames.length && method == null; i++)
187             if (mRemoteInvocationHandlers.get(interfaceClassNames[i]) != null)
188                 try
189                 {
190                     method = this.findMatchingMethod(Class.forName(interfaceClassNames[i]), methodName, arguments);
191                     if (method != null)
192                         target = mRemoteInvocationHandlers.get(interfaceClassNames[i]);
193                 }
194                 catch (ClassNotFoundException e)
195                 {
196                     Log.printError(this, "Couldn't find class", e);
197                 }
198         
199         try
200         {
201             // Invoke the method on the target
202             Log.print(this, "Invoking method...");
203             Object returnValue = method.invoke(target, arguments);
204             Log.print(this, "done!");
205             if (returnValue instanceof IDistributable)
206                 returnValue = this.createProxy(returnValue);
207             return new DynamicRemoteProxy.DefaultProxyReturnMessage(returnValue);
208         }
209         catch (InvocationTargetException targetException)
210         {
211             Log.printError(this, "Couldn't invoke target method", targetException);
212             return new DynamicRemoteProxy.DefaultProxyExceptionMessage(targetException.getTargetException().getClass(), targetException.getTargetException().getMessage());
213         }
214         catch (Throwable t)
215         {
216             Log.printError(this, "Unknown error", t);
217             return new DynamicRemoteProxy.DefaultProxyExceptionMessage(t.getClass(), t.getMessage());
218         }
219     }
220     
221     // Help methods ------------------------------------------------------------
222     protected synchronized int generateProxyID()
223     {
224         if (mCurrentProxyID < Integer.MAX_VALUE)
225             mCurrentProxyID++;
226         else
227             mCurrentProxyID = 1;
228         
229         return mCurrentProxyID;
230     }
231     
232     protected Method findMatchingMethod(Class targetClass, String methodName, Object[] arguments)
233     {
234         Method method = null;
235         
236         try
237         {
238             Method[] methods = targetClass.getMethods();
239             for (int m = 0; m < methods.length && method == null; m++)
240             {
241                 if (methods[m].getName().compareTo(methodName) == 0)
242                 {
243                     // The method name is correct, now check the method arguments
244                     Class[] paramClasses = methods[m].getParameterTypes();
245                     if (arguments == null && paramClasses.length > 0 || 
246                         arguments != null && paramClasses.length != arguments.length)
247                         // Wring number of arguments, continue with next method
248                         continue;
249 
250                     // Check the type of the arguments
251                     boolean match = true;
252                     for (int c = 0; c < paramClasses.length && match; c++)
253                         if (arguments[c] != null && !paramClasses[c].isInstance(arguments[c]))
254                             match = false;
255                     if (match)
256                         method = methods[m];
257                 }
258             }
259         }
260         catch (Exception e)
261         {
262         }
263         
264         return method;
265     }
266 }