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.BufferedReader;
20  import java.io.IOException;
21  import java.io.InputStreamReader;
22  import java.io.Reader;
23  import java.net.ConnectException;
24  import java.net.HttpURLConnection;
25  import java.net.MalformedURLException;
26  import java.net.SocketException;
27  import java.net.URL;
28  import java.net.URLConnection;
29  import java.util.Formatter;
30  import java.util.HashMap;
31  import java.util.Map;
32  
33  import javax.net.ssl.SSLHandshakeException;
34  
35  import org.apache.axis.AxisProperties;
36  import org.apache.commons.io.IOUtils;
37  
38  import com.gridsystems.innergrid.kernel.KernelException;
39  
40  /**
41   * Connects to a single server.
42   *
43   * @author Rodrigo Ruiz
44   * @author Xmas
45   */
46  public abstract class AbstractConnection implements Connection {
47  
48    /**
49     * Maps Credentials classes to Base URL templates.
50     */
51    private static final Map<Class<?>, String> PATH_TEMPLATES;
52  
53    static {
54      PATH_TEMPLATES = new HashMap<Class<?>, String>();
55      PATH_TEMPLATES.put(UsernameTokenCredentials.class, "/kernel/api/%s");
56      PATH_TEMPLATES.put(X509CertificateCredentials.class, "/kernel/api/%s_cert");
57      PATH_TEMPLATES.put(null, "/kernel/api/%s");
58    }
59  
60    /**
61     * Default retries.
62     */
63    public static final int DEFAULT_RETRIES = 5;
64  
65    /**
66     * Host to connect.
67     */
68    protected String host = null;
69  
70    /**
71     * Port to connect.
72     */
73    protected int port = -1;
74  
75    /**
76     * Web-application context path base.
77     */
78    protected String contextPath;
79  
80    /**
81     * Secured connection.
82     */
83    protected boolean secured = false;
84  
85    /**
86     * SSL Connection Info.
87     */
88    protected SSLConnectionInfo sslInfo = null;
89  
90    /**
91     * The credentials to use.
92     */
93    protected Credentials credentials = null;
94  
95    /**
96     * Connection timeout.
97     */
98    protected int timeout = DEFAULT_TIMEOUT;
99  
100   /**
101    * Chunked Transfer encoding control flag.
102    */
103   protected boolean chunkedTransferEnabled = true;
104 
105   /**
106    * Keep-Alive control flag.
107    */
108   protected boolean keepAliveEnabled = false;
109 
110   /**
111    * Attachments format.
112    */
113   protected String attachmentFormat;
114 
115   /**
116    * Number of retries.
117    */
118   protected int retries = DEFAULT_RETRIES;
119 
120   /**
121    * Flag that avoid repeating the connection test.
122    */
123   protected boolean connChecked = false;
124 
125   /**
126    * To synchronise host, port, and secured access.
127    */
128   protected Object sync = new Object();
129 
130   static {
131     // This makes the client sockets to disable their SO_LINGER option flag
132     AxisProperties.setProperty("axis.socketFactory",
133       NoLingerSocketFactory.class.getName(), true);
134 
135     // This makes the client to accept Grid Certificates
136     AxisProperties.setProperty("axis.socketSecureFactory",
137         InnergridJSSESocketFactory.class.getName(), true);
138   }
139 
140   /**
141    * Gets the default Context Path Template for the specified Credentials
142    * instance.
143    *
144    * @param cred Credentials instance
145    * @return Context path template
146    */
147   private static String getDefaultPathFor(Credentials cred) {
148     String path = null;
149     if (cred != null) {
150       path = PATH_TEMPLATES.get(cred.getClass());
151     }
152 
153     if (path == null) {
154       path = PATH_TEMPLATES.get(null);
155     }
156 
157     return path;
158   }
159 
160 
161   /**
162    * Creates an instance of the connector to the specified host:port, and
163    * using the specified credentials.
164    *
165    * @param h           The remote server host name or address
166    * @param p           The remote server port
167    * @param ctxPath     Context path base
168    * @param secure      Whether to use a secure protocol(HTTPS) or not
169    * @param sslinfo     Additional info to establish SSL connection
170    * @param credentials The user name.
171    */
172   public AbstractConnection(String h, int p, String ctxPath, boolean secure,
173     SSLConnectionInfo sslinfo, Credentials credentials) {
174     this.host = h;
175     this.port = p;
176     this.contextPath = (ctxPath == null) ? getDefaultPathFor(credentials) : ctxPath;
177     this.secured = secure;
178     if (this.secured) {
179       if (sslinfo == null) {
180         this.sslInfo = new AcceptAllCertificates();
181       } else {
182         this.sslInfo = sslinfo;
183       }
184       InnergridJSSESocketFactory.registrySSLConnectionInfo(h, p, sslInfo);
185     }
186     this.credentials = credentials;
187   }
188 
189   /**
190    * Creates an instance of the connector to the specified host:port, and
191    * using the specified credentials.
192    *
193    * @param h           The remote server host name or address
194    * @param p           The remote server port
195    * @param secure      Whether to use a secure protocol(HTTPS) or not
196    * @param sslinfo     Additional info to establish SSL connection
197    * @param credentials The user name.
198    */
199   public AbstractConnection(String h, int p, boolean secure,
200     SSLConnectionInfo sslinfo, Credentials credentials) {
201     this(h, p, null, secure, sslinfo, credentials);
202   }
203 
204   /**
205    * Gets Host to connect.
206    * @return Host to connect.
207    */
208   public String getHost() {
209     synchronized (sync) {
210       return this.host;
211     }
212   }
213 
214   /**
215    * Gets Port to connect.
216    * @return Port to connect.
217    */
218   public int getPort() {
219     synchronized (sync) {
220       return this.port;
221     }
222   }
223 
224   /**
225    * {@inheritDoc}
226    */
227   public String getContextPath() {
228     synchronized (sync) {
229       return this.contextPath;
230     }
231   }
232 
233   /**
234    * Sets the context path template.
235    *
236    * @param ctxPath Template
237    */
238   public void setContextPath(String ctxPath) {
239     if (ctxPath == null) {
240       ctxPath = getDefaultPathFor(this.credentials);
241     }
242     this.contextPath = ctxPath;
243   }
244 
245   /**
246    * Use a secured connection.
247    * @return true if this connection uses SSL.
248    */
249   public boolean isSecured() {
250     synchronized (sync) {
251       return this.secured;
252     }
253   }
254 
255   /**
256    * {@inheritDoc}
257    */
258   public int getTimeout() {
259     synchronized (sync) {
260       return this.timeout;
261     }
262   }
263 
264   /**
265    * {@inheritDoc}
266    */
267   public void setTimeout(int timeout) {
268     synchronized (sync) {
269       this.timeout = timeout;
270     }
271   }
272 
273   /**
274    * {@inheritDoc}
275    */
276   public boolean isChunkedTransferEnabled() {
277     synchronized (sync) {
278       return this.chunkedTransferEnabled;
279     }
280   }
281 
282   /**
283    * {@inheritDoc}
284    */
285   public void setChunkedTransferEnabled(boolean enabled) {
286     synchronized (sync) {
287       this.chunkedTransferEnabled = enabled;
288     }
289   }
290 
291   /**
292    * {@inheritDoc}
293    */
294   public boolean isKeepAliveEnabled() {
295     synchronized (sync) {
296       return this.keepAliveEnabled;
297     }
298   }
299 
300   /**
301    * {@inheritDoc}
302    */
303   public void setKeepAliveEnabled(boolean enabled) {
304     synchronized (sync) {
305       this.keepAliveEnabled = enabled;
306     }
307   }
308 
309   /**
310    * {@inheritDoc}
311    */
312   public String getAttachmentFormat() {
313     synchronized (sync) {
314       return this.attachmentFormat;
315     }
316   }
317 
318   /**
319    * {@inheritDoc}
320    */
321   public void setAttachmentFormat(String format) {
322     synchronized (sync) {
323       this.attachmentFormat = format;
324     }
325   }
326 
327   /**
328    * Gets retries.
329    * @return Return retries value.
330    */
331   public int getRetries() {
332     return this.retries;
333   }
334 
335   /**
336    * Sets retires.
337    * @param retries New value for retries
338    */
339   public void setRetries(int retries) {
340     this.retries = retries;
341   }
342 
343   /**
344    * {@inheritDoc}
345    *
346    */
347   public URL getUrl(String apiName) throws KernelException {
348     String baseUrl = getBaseUrl();
349     try {
350       Formatter f = new Formatter();
351       f.format(baseUrl, apiName);
352       return new URL(f.toString());
353     } catch (MalformedURLException e) {
354       throw new CKernelException(e, "CLT008", "[ " + baseUrl + ", " + apiName + " ]");
355     }
356   }
357 
358   /**
359    * {@inheritDoc}
360    */
361   public Credentials getCredentials() {
362     return this.credentials;
363   }
364 
365   /**
366    * Obtain additional information to establish SSL connection.
367    * @return SLL Connection Info
368    */
369   public SSLConnectionInfo getSSLConnectionInfo() {
370     return sslInfo;
371   }
372 
373   /**
374    * Gets the base URL for all API URLs.
375    *
376    * @return  The base URL
377    * @throws KernelException If the URL is not well formed
378    */
379   public abstract String getBaseUrl() throws KernelException;
380 
381   /**
382    * Manage Exceptions.
383    *
384    * @param ke KernelException captured
385    * @param retry Number of retry
386    * @throws KernelException Throws ke exception if do not desire retry.
387    *
388    */
389   public abstract void manageException(KernelException ke, int retry)
390     throws KernelException;
391 
392   /**
393    * Avoids checking the connection data.
394    * <p>
395    * When the connection is used the first time, it performs a test to find out
396    * if the data (specially the protocol used) is right. Calling this method
397    * avoids calling the tests, improving the connection performance for that
398    * first time.
399    */
400   public final void clearChecks() {
401     this.connChecked = true;
402   }
403 
404   /**
405    * {@inheritDoc}
406    */
407   public final void checkConnection() throws KernelException {
408     if (!connChecked && !checkPort(secured)) {
409       String url = this.host + ":" + this.port;
410       String[] protocols = { "http", "https" };
411       int i = secured ? 1 : 0;
412       throw new CKernelException("CLT061", protocols[i], url, protocols[1 - i]);
413     }
414     connChecked = true;
415   }
416 
417   /**
418    * Checks that the connection port can be reached in a safe/unsafe way.
419    *
420    * @param secure <code>true</code> if <code>https</code> protocol is to be checked;
421    *               <code>false</code> if the protocol to be used is <code>http</code>.
422    * @return true if and only if the port was reached and the stream obtained
423    *         was formed by "normal" text.
424    * @throws KernelException in case of error reaching for the port.
425    */
426   private boolean checkPort(boolean secure) throws KernelException {
427     // Opening an HTTPS connection when the remote port is HTTP would hang the thread
428     if (secure && checkPort(false)) {
429       return false;
430     }
431 
432     BufferedReader reader = null;
433     URLConnection con = null;
434     try {
435       URL url = new URL(secure ? "https" : "http", this.host, this.port, "");
436       con = url.openConnection();
437       con.setUseCaches(false);
438 
439       reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
440 
441       // A successful connection will return a mix of printable and control characters.
442       // The following code controls the case where the connection returned only
443       // control characters, which is an indication of a corrupted stream
444       return test(reader);
445 
446     } catch (MalformedURLException e) {
447       throw new CKernelException(e, "CLT062", "malformed");
448     } catch (ConnectException e) {
449       throw new CKernelException(e, "CLT000", host + ":" + port);
450     } catch (SocketException e) {
451       return false;
452     } catch (SSLHandshakeException e) {
453       // A Handshake error means there is SSL at both sides
454       return this.secured;
455     } catch (IOException ioe) {
456       if (ioe.getMessage().indexOf("hostname wrong") != -1) {
457         return secure;
458       }
459 
460       // Unexpected errors are converted to KernelExceptions
461       throw new CKernelException(ioe, "CLT062", ioe.getMessage());
462     } catch (Throwable t) {
463       // Unexpected errors are converted to KernelExceptions
464       throw new CKernelException(t, "CLT062", t.getMessage());
465     } finally {
466       IOUtils.closeQuietly(reader);
467       if (con instanceof HttpURLConnection) {
468         ((HttpURLConnection)con).disconnect();
469       }
470     }
471   }
472 
473   /**
474    * Reads the reader contents and searches for non-control characters in its stream.
475    *
476    * @param reader  The reader to read from
477    * @return        <code>true</code> if it contains at least one non-control character
478    * @throws IOException If an I/O error occurs
479    */
480   private boolean test(Reader reader) throws IOException {
481     char ch = (char)reader.read();
482     while (ch != -1) {
483       if (!Character.isISOControl(ch)) {
484         return true;
485       }
486       ch = (char)reader.read();
487     }
488     return false;
489   }
490 }