View Javadoc

1   /*
2   Copyright (C) 2000 - 2007 Grid Systems, S.A.
3   
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License, version 2, as
6   published by the Free Software Foundation.
7   
8   This program 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
11  GNU General Public License for more details.
12  
13  You should have received a copy of the GNU General Public License
14  along with this program; if not, write to the Free Software
15  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16  */
17  package com.gridsystems.innergrid.api;
18  
19  import java.lang.reflect.InvocationHandler;
20  import java.lang.reflect.InvocationTargetException;
21  import java.lang.reflect.Method;
22  import java.net.ConnectException;
23  import java.net.SocketException;
24  import java.net.URL;
25  import java.net.UnknownHostException;
26  import java.rmi.RemoteException;
27  import java.util.HashSet;
28  import java.util.Map;
29  import java.util.Set;
30  import java.util.StringTokenizer;
31  import java.util.regex.Matcher;
32  import java.util.regex.Pattern;
33  
34  import javax.xml.rpc.Stub;
35  
36  import org.apache.axis.AxisFault;
37  import org.apache.axis.MessageContext;
38  import org.apache.axis.client.Call;
39  import org.apache.axis.transport.http.HTTPConstants;
40  
41  import com.gridsystems.innergrid.kernel.KernelException;
42  
43  /**
44   * Dynamic proxy InvocationHandler class.
45   * <p>
46   * This class wraps a Stub and Connection objects. Each time a method is
47   * invoked on a dynamic proxy using this invocation handler, the Connection
48   * is used to configure the Stub, control call retries, and manage exceptions.
49   *
50   * @author Rodrigo Ruiz
51   */
52  public class ClientApiProxy extends HTTPConstants implements InvocationHandler {
53  
54    /**
55     * Set of "low-level" interfaces.
56     */
57    private static final Set<Class<?>> IFACES = new HashSet<Class<?>>();
58  
59    static {
60      IFACES.add(Object.class);
61      IFACES.add(javax.xml.rpc.Stub.class);
62      IFACES.add(org.apache.axis.client.Stub.class);
63    }
64  
65    /**
66     * Stub instance.
67     */
68    private final Stub stub;
69  
70    /**
71     * The connection to use.
72     */
73    private final Connection con;
74  
75    /**
76     * The API name.
77     */
78    private final String apiName;
79  
80    /**
81     * Constructor - package access only.
82     * <p>
83     * Should only be called from ApiFactory.
84     *
85     * @param iface The interface being proxied
86     * @param stub  The stub to wrap
87     * @param con   The connection
88     */
89    ClientApiProxy(Class<?> iface, Stub stub, Connection con) {
90      this.stub = stub;
91      this.con = con;
92  
93      // Retrieve the API name
94      String name = iface.getSimpleName();
95      int pos = name.lastIndexOf('.');
96      name = name.substring(pos + 1);
97      this.apiName = name;
98    }
99  
100   /**
101    * Handle a method invocation.
102    *
103    * @param o         the object to invoke relative to
104    * @param method    the <code>Method</code> to invoke
105    * @param args      the arguments to the method
106    * @return  the result of the method
107    * @throws Throwable if anything went wrong in method dispatching or the
108    *              execution of the method itself
109    */
110   public Object invoke(Object o, Method method, Object[] args)
111     throws Throwable {
112 
113     // Synchronized for thread safety
114     synchronized (stub) {
115       if (IFACES.contains(method.getDeclaringClass())) {
116         // Filter "local" methods
117         return method.invoke(stub, args);
118       } else {
119 
120         int retry = 0;
121         while (true) {
122           retry++;
123           try {
124             setup();
125             Object object = method.invoke(stub, args);
126             con.success();
127             return object;
128           } catch (Exception e) {
129             con.manageException(processException(e), retry);
130           }
131         }
132       }
133     }
134   }
135 
136   /**
137    * Configures the stub for its usage.
138    *
139    * @throws KernelException If an error occurs
140    */
141   @SuppressWarnings("unchecked")
142   private void setup() throws KernelException {
143     // Activate HTTP1.1 and chunked transfer encoding
144     Map headers = (Map)stub._getProperty(REQUEST_HEADERS);
145     if (con.isChunkedTransferEnabled()) {
146       headers.put(HEADER_TRANSFER_ENCODING, HEADER_TRANSFER_ENCODING_CHUNKED);
147     } else {
148       headers.remove(HEADER_TRANSFER_ENCODING);
149     }
150     stub._setProperty(MessageContext.HTTP_TRANSPORT_VERSION, HEADER_PROTOCOL_V11);
151 
152     // Configure Keep-Alive Connections
153     if (con.isKeepAliveEnabled()) {
154       stub._setProperty(HEADER_CONNECTION, HEADER_CONNECTION_KEEPALIVE);
155     } else {
156       stub._setProperty(HEADER_CONNECTION, HEADER_CONNECTION_CLOSE);
157     }
158 
159     // Set socket timeout
160     stub._setProperty(Call.CONNECTION_TIMEOUT_PROPERTY, con.getTimeout());
161 
162     // Configure attachment format
163     if (con.getAttachmentFormat() != null) {
164       stub._setProperty(Call.ATTACHMENT_ENCAPSULATION_FORMAT, con.getAttachmentFormat());
165     }
166 
167     Credentials credentials = this.con.getCredentials();
168     if (credentials != null) {
169       credentials.setup(stub);
170     }
171     URL url = this.con.getUrl(apiName);
172     stub._setProperty(Stub.ENDPOINT_ADDRESS_PROPERTY, url.toString());
173   }
174 
175   /**
176    * Processes incoming exceptions and converts them to the correct KernelException
177    * instance.
178    *
179    * @param e The exception to process
180    * @return   An equivalent KernelException instance
181    */
182   private KernelException processException(Throwable e) {
183 
184     if (e instanceof InvocationTargetException) {
185       e = ((InvocationTargetException)e).getTargetException();
186     }
187     if (e instanceof KernelException) {
188       return (KernelException)e;
189     } else if (e instanceof AxisFault) {
190       AxisFault af = (AxisFault) e;
191 
192       Throwable cause = af.getCause();
193       if (cause != null) {
194         String host = this.con.getHost();
195         String port = String.valueOf(this.con.getPort());
196 
197         // Client Exception
198         if (cause instanceof ConnectException) {
199           //  -- Host exists but the port is not open
200           return new CKernelException(e, "CLT001", port, host);
201         } else if (cause instanceof UnknownHostException) {
202           //  -- Cannot found server
203           return new CKernelException(e, "CLT000", host);
204         } else if (cause instanceof SocketException) {
205           // -- Connection closed by server
206           return new CKernelException(e, "CLT002", host);
207         }
208       }
209       if (af.getFaultCode().getLocalPart().equals("Server.NoService")) {
210 
211         String reason = af.getFaultReason();
212         String apiname = "?";
213         if (reason != null) {
214           int pos = reason.lastIndexOf(' ');
215           if (pos != -1) {
216             apiname = reason.substring(pos + 1);
217           }
218         }
219         return new CKernelException(e, "CLT003", apiname);
220       } else if (af.getFaultCode().getLocalPart().equals("CLT004")) {
221         return new CKernelException(e, "CLT004", e.getMessage());
222       } else if (af.getFaultString().indexOf(
223         "could not find deserializer for type") != -1) {
224         return getDeserializationException(af);
225       } else if (af.getFaultString().startsWith("(404)/kernel/api")) {
226         return new CKernelException("CLT006");
227       }
228     }
229 
230     if (e instanceof RemoteException) {
231       RemoteException re = (RemoteException) e;
232       return KernelException.fromRemoteException(re);
233     } else {
234       // Unknown Error $0
235       return new CKernelException(e, "UNK000", e.getMessage());
236     }
237   }
238 
239   /**
240    * Provides a CKernelException with the info of a deserialization error.
241    *
242    * Provides a CKernelException with the info of a deserialization error. It
243    * will try to parse the error message to find the parameters for the
244    * exception message.
245    *
246    * @param axisFault the AxisFault original.
247    *
248    * @return a CKernelException with more accurate data of the error that has
249    * happened.
250    */
251   private CKernelException getDeserializationException(AxisFault axisFault) {
252     // Number of resulting groups for a valid matching
253     // This definition prevents several CheckStyle warnings
254     final int groupCount = 3;
255 
256     Pattern pat = Pattern.compile("'in(\\d+).+http\\:\\/\\/([\\w\\.]*)}(\\w+)$");
257     Matcher mat = pat.matcher(axisFault.getFaultString());
258     String[] params = new String[2];
259     if (mat.find() && (mat.groupCount() == groupCount)) {
260       params[0] = mat.group(1);
261       // The string provides the package name in reverse form (as an URL). Here
262       // we put it back in its original state.
263       String packageName = namespaceToClass(mat.group(2), mat.group(groupCount));
264       params[1] = packageName + mat.group(groupCount);
265     } else {
266       params[0] = "?";
267       params[1] = "?";
268     }
269     return new CKernelException(axisFault, "CLT005", params);
270   }
271 
272   /**
273    * Converts a namespace and local name to a class name. The local name is the
274    * unqualified class name. The namespace is the class package name in reverse order.
275    *
276    * @param namespace  The namespace
277    * @param localName  The local name
278    * @return The associated class name
279    */
280   private String namespaceToClass(String namespace, String localName) {
281     StringBuffer sb = new StringBuffer(localName);
282     for (StringTokenizer st = new StringTokenizer(namespace, "."); st.hasMoreTokens();) {
283       sb.insert(0, ".");
284       sb.insert(0, st.nextToken());
285     }
286     return sb.toString();
287   }
288 
289   /**
290    * Gets the connection.
291    *
292    * @return The connection
293    */
294   public Connection getConnection() {
295     return this.con;
296   }
297 
298   /**
299    * Initialises this class.
300    * <p>
301    * This method does not really do anything. Initialisation is performed in
302    * a static anonymous block, and only once. This method is a hook to allow
303    * programmers to load and initialise this class before using it.
304    */
305   public static void init() {
306   }
307 }