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.io.File;
20  import java.net.ConnectException;
21  import java.net.SocketException;
22  import java.net.URL;
23  import java.net.UnknownHostException;
24  import java.rmi.RemoteException;
25  import java.util.Hashtable;
26  import java.util.StringTokenizer;
27  import java.util.regex.Matcher;
28  import java.util.regex.Pattern;
29  
30  import javax.activation.DataHandler;
31  import javax.activation.DataSource;
32  import javax.activation.FileDataSource;
33  
34  import org.apache.axis.AxisFault;
35  import org.apache.axis.MessageContext;
36  import org.apache.axis.EngineConfiguration;
37  import org.apache.axis.configuration.FileProvider;
38  import org.apache.axis.client.AxisClient;
39  import org.apache.axis.client.Stub;
40  import org.apache.axis.transport.http.HTTPConstants;
41  
42  import com.gridsystems.innergrid.kernel.KernelException;
43  
44  /**
45   * Base implementation for SOAP connectors.
46   *
47   * @author Xmas
48   * @version 1.0
49   */
50  public abstract class AbstractImplBase {
51  
52    /**
53     * Singleton engine configuration.
54     */
55    public static final EngineConfiguration CONFIG;
56  
57    /**
58     * Singleton client engine.
59     */
60    public static final AxisClient ENGINE;
61  
62    static {
63      // Fura client APIs use their own configuration
64      CONFIG = new FileProvider("apifactory-client-config.wsdd");
65      ENGINE = new AxisClient(CONFIG);
66    }
67  
68    /**
69     * The connection to use.
70     */
71    protected Connection conn = null;
72  
73    /**
74     * The name of the remote API.
75     */
76    protected String apiName = null;
77  
78    /**
79     * The wrapped stub.
80     */
81    protected Stub stub = null;
82  
83    /**
84     * The current URL assigned to the stub.
85     */
86    protected URL currentUrl;
87  
88    /**
89     * Creates a new instance associated to the specified connection, and with the
90     * given name.
91     *
92     * @param c       The connection to use
93     * @param apiName The API name to set
94     * @throws KernelException <code>CLT011</code> If the connection is null
95     * @throws KernelException <code>CLT014</code> If the API name is null or empty
96     */
97    public AbstractImplBase(Connection c, String apiName) throws KernelException {
98      if (c == null) {
99        throw new CKernelException("CLT011");
100     }
101     if (apiName == null || apiName.equals("")) {
102       throw new CKernelException("CLT014");
103     }
104 
105     this.conn = c;
106     this.apiName = apiName;
107   }
108 
109   /**
110    * Create a stub instance of the appropriate class for the specific subclass
111    * implementation.
112    *
113    * @param url The target service url
114    * @return A newly created Stub instance
115    * @throws AxisFault If an error occurs creating the stub
116    */
117   protected abstract Stub axisCreateStub(URL url) throws AxisFault;
118 
119   /**
120    * Gets the stub instance to use in remote calls.
121    *
122    * @return The stub instance
123    * @throws KernelException If an error occurs creating the stub
124    */
125   protected Stub axisGetStub() throws KernelException {
126     URL newUrl = this.conn.getUrl(apiName);
127     if (this.stub == null || !newUrl.equals(currentUrl)) {
128       try {
129         stub = axisCreateStub(newUrl);
130       } catch (AxisFault af) {
131         throw processException(af);
132       }
133 
134       // Store the new stub data in the ThreadLocal fields
135       currentUrl = newUrl;
136 
137       // Initialize the stub
138       axisInitStub(stub);
139     }
140 
141     // Configure the stub
142     axisConfigStub(stub);
143 
144     return stub;
145   }
146 
147   /**
148    * Initializes the stub. This method will be called only once per stub instance.
149    * <p>
150    * The default initialization activates HTTP/1.1 protocol with chunked transfer
151    * encoding and non-persistent connections. It also sets the connection timeout to
152    * five minutes.
153    * <p>
154    * Chunked transfer encoding is needed to allow the server to detect corrupted or
155    * incomplete requests, and return the appropriate exception. If transfer encoding
156    * is not set to this value, the server will not be able to detect this condition, and
157    * the processor thread will hang until the client response read times out.
158    *
159    * @param stub The stub to initialise
160    */
161   @SuppressWarnings("unchecked")
162   protected void axisInitStub(Stub stub) {
163     Hashtable headers = (Hashtable)stub._getProperty(HTTPConstants.REQUEST_HEADERS);
164     if (headers == null) {
165       headers = new Hashtable();
166       stub._setProperty(HTTPConstants.REQUEST_HEADERS, headers);
167     }
168 
169     // Activate HTTP1.1 protocol
170     stub._setProperty(MessageContext.HTTP_TRANSPORT_VERSION,
171         HTTPConstants.HEADER_PROTOCOL_V11);
172 
173   }
174 
175   /**
176    * Configures the stub. This method will be called before each remote call.
177    *
178    * @param stub The stub to configure
179    */
180   @SuppressWarnings("unchecked")
181   protected void axisConfigStub(Stub stub) {
182     Hashtable headers = (Hashtable)stub._getProperty(HTTPConstants.REQUEST_HEADERS);
183     if (conn.isChunkedTransferEnabled()) {
184       headers.put(HTTPConstants.HEADER_TRANSFER_ENCODING,
185                   HTTPConstants.HEADER_TRANSFER_ENCODING_CHUNKED);
186     } else {
187       headers.remove(HTTPConstants.HEADER_TRANSFER_ENCODING);
188     }
189 
190     String conType = (conn.isKeepAliveEnabled())
191                        ? HTTPConstants.HEADER_CONNECTION_KEEPALIVE
192                        : HTTPConstants.HEADER_CONNECTION_CLOSE;
193 
194     stub._setProperty(HTTPConstants.HEADER_CONNECTION, conType);
195     stub.setTimeout(conn.getTimeout());
196 
197     Credentials credentials = this.conn.getCredentials();
198     if (credentials != null) {
199       credentials.setup(stub);
200     }
201   }
202 
203   /**
204    * Iterates over an array of attachment parameters and checks that, if
205    * they are associated to a File instance, the file really exists.
206    *
207    * @param params The list of parameters of attachment type
208    * @throws KernelException CLT064 Attachment associated to a non-existent file
209    */
210   protected void axisCheckAttachments(Object[] params) throws KernelException {
211     int count = (params == null) ? 0 : params.length;
212     for (int i = 0; i < count; i++) {
213       File f = null;
214       if (params[i] instanceof FileDataSource) {
215         f = ((FileDataSource)params[i]).getFile();
216       } else if (params[i] instanceof DataHandler) {
217         DataSource ds = ((DataHandler)params[i]).getDataSource();
218         if (ds instanceof FileDataSource) {
219           f = ((FileDataSource)ds).getFile();
220         }
221       } else if (params[i] instanceof File) {
222         f = (File)params[i];
223       }
224 
225       if (f != null && !f.exists()) {
226         throw new CKernelException("CLT021", f.getPath());
227       }
228     }
229   }
230   /**
231    * Processes incoming exceptions and converts them to the correct KernelException
232    * instance.
233    *
234    * @param e The exception to process
235    * @return   An equivalent KernelException instance
236    */
237   protected KernelException processException(Exception e) {
238 
239     if (e instanceof KernelException) {
240       return (KernelException)e;
241     } else if (e instanceof AxisFault) {
242       AxisFault af = (AxisFault) e;
243 
244       Throwable cause = af.getCause();
245       if (cause != null) {
246         String host = this.conn.getHost();
247         String port = String.valueOf(this.conn.getPort());
248 
249         // Client Exception
250         if (cause instanceof ConnectException) {
251           //  -- Host exists but the port is not open
252           return new CKernelException(e, "CLT001", port, host);
253         } else if (cause instanceof UnknownHostException) {
254           //  -- Cannot found server
255           return new CKernelException(e, "CLT000", host);
256         } else if (cause instanceof SocketException) {
257           // -- Connection closed by server
258           return new CKernelException(e, "CLT002", host);
259         }
260       }
261       if (af.getFaultCode().getLocalPart().equals("Server.NoService")) {
262 
263         String reason = af.getFaultReason();
264         String apiname = "?";
265         if (reason != null) {
266           int pos = reason.lastIndexOf(' ');
267           if (pos != -1) {
268             apiname = reason.substring(pos + 1, reason.length());
269           }
270         }
271         return new CKernelException(e, "CLT003", apiname);
272       } else if (af.getFaultCode().getLocalPart().equals("CLT004")) {
273         return new CKernelException(e, "CLT004", e.getMessage());
274       } else if (af.getFaultString().indexOf(
275         "could not find deserializer for type") != -1) {
276         return getDeserializationException(af);
277       } else if (af.getFaultString().startsWith("(404)/kernel/api")) {
278         return new CKernelException("CLT006");
279       }
280     }
281 
282     if (e instanceof RemoteException) {
283       RemoteException re = (RemoteException) e;
284       return KernelException.fromRemoteException(re);
285     }
286 
287     // Unknown Error $0
288     return new CKernelException(e, "UNK000", e.getMessage());
289   }
290 
291   /**
292    * Provides a CKernelException with the info of a deserialization error.
293    *
294    * Provides a CKernelException with the info of a deserialization error. It
295    * will try to parse the error message to find the parameters for the
296    * exception message.
297    *
298    * @param axisFault the AxisFault original.
299    *
300    * @return a CKernelException with more accurate data of the error that has
301    * happened.
302    */
303   private CKernelException getDeserializationException(AxisFault axisFault) {
304     // Number of resulting groups for a valid matching
305     // This definition prevents several CheckStyle warnings
306     final int groupCount = 3;
307 
308     Pattern pat = Pattern.compile("'in(\\d+).+http\\:\\/\\/([\\w\\.]*)}(\\w+)$");
309     Matcher mat = pat.matcher(axisFault.getFaultString());
310     String[] params = new String[2];
311     if (mat.find() && (mat.groupCount() == groupCount)) {
312       params[0] = mat.group(1);
313       // The string provides the package name in reverse form (as an URL). Here
314       // we put it back in its original state.
315       String packageName = namespaceToClass(mat.group(2), mat.group(groupCount));
316       params[1] = packageName + mat.group(groupCount);
317     } else {
318       params[0] = "?";
319       params[1] = "?";
320     }
321     return new CKernelException(axisFault, "CLT005", params);
322   }
323 
324   /**
325    * Converts a namespace and local name to a class name. The local name is the
326    * unqualified class name. The namespace is the class package name in reverse order.
327    *
328    * @param namespace  The namespace
329    * @param localName  The local name
330    * @return The associated class name
331    */
332   private String namespaceToClass(String namespace, String localName) {
333     StringBuffer sb = new StringBuffer(localName);
334     for (StringTokenizer st = new StringTokenizer(namespace, "."); st.hasMoreTokens();) {
335       sb.insert(0, ".");
336       sb.insert(0, st.nextToken());
337     }
338     return sb.toString();
339   }
340 }