View Javadoc

1   /*
2    * $HeadURL: https://svn.apache.org/repos/asf/httpcomponents/oac.hc3x/trunk/src/java/org/apache/commons/httpclient/protocol/ReflectionSocketFactory.java $
3    * $Revision: 1425331 $
4    * $Date: 2012-12-22 19:29:41 +0100 (Sat, 22 Dec 2012) $
5    *
6    * ====================================================================
7    *
8    *  Licensed to the Apache Software Foundation (ASF) under one or more
9    *  contributor license agreements.  See the NOTICE file distributed with
10   *  this work for additional information regarding copyright ownership.
11   *  The ASF licenses this file to You under the Apache License, Version 2.0
12   *  (the "License"); you may not use this file except in compliance with
13   *  the License.  You may obtain a copy of the License at
14   *
15   *      http://www.apache.org/licenses/LICENSE-2.0
16   *
17   *  Unless required by applicable law or agreed to in writing, software
18   *  distributed under the License is distributed on an "AS IS" BASIS,
19   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20   *  See the License for the specific language governing permissions and
21   *  limitations under the License.
22   * ====================================================================
23   *
24   * This software consists of voluntary contributions made by many
25   * individuals on behalf of the Apache Software Foundation.  For more
26   * information on the Apache Software Foundation, please see
27   * <http://www.apache.org/>.
28   *
29   */
30  
31  package org.apache.commons.httpclient.protocol;
32  
33  import java.io.IOException;
34  import java.lang.reflect.Constructor;
35  import java.lang.reflect.InvocationTargetException;
36  import java.lang.reflect.Method;
37  import java.net.InetAddress;
38  import java.net.Socket;
39  import java.net.UnknownHostException;
40  
41  import org.apache.commons.httpclient.ConnectTimeoutException;
42  
43  /***
44   * This helper class uses refelction in order to execute Socket methods
45   * available in Java 1.4 and above  
46   * 
47   * @author Oleg Kalnichevski
48   * 
49   * @since 3.0
50   */
51  public final class ReflectionSocketFactory {
52  
53      private static boolean REFLECTION_FAILED = false;
54      
55      private static Constructor INETSOCKETADDRESS_CONSTRUCTOR = null;
56      private static Method SOCKETCONNECT_METHOD = null;
57      private static Method SOCKETBIND_METHOD = null;
58      private static Class SOCKETTIMEOUTEXCEPTION_CLASS = null;
59  
60      private ReflectionSocketFactory() {
61          super();
62      }
63  
64      /***
65       * This method attempts to execute Socket method available since Java 1.4
66       * using reflection. If the methods are not available or could not be executed
67       * <tt>null</tt> is returned
68       *   
69       * @param socketfactoryName name of the socket factory class
70       * @param host the host name/IP
71       * @param port the port on the host
72       * @param localAddress the local host name/IP to bind the socket to
73       * @param localPort the port on the local machine
74       * @param timeout the timeout value to be used in milliseconds. If the socket cannot be
75       *        completed within the given time limit, it will be abandoned
76       * 
77       * @return a connected Socket
78       * 
79       * @throws IOException if an I/O error occurs while creating the socket
80       * @throws UnknownHostException if the IP address of the host cannot be
81       * determined
82       * @throws ConnectTimeoutException if socket cannot be connected within the
83       *  given time limit
84       * 
85       */
86      public static Socket createSocket(
87          final String socketfactoryName, 
88          final String host,
89          final int port,
90          final InetAddress localAddress,
91          final int localPort,
92          int timeout)
93       throws IOException, UnknownHostException, ConnectTimeoutException
94      {
95          if (REFLECTION_FAILED) {
96              //This is known to have failed before. Do not try it again
97              return null;
98          }
99          // This code uses reflection to essentially do the following:
100         //
101         //  SocketFactory socketFactory = Class.forName(socketfactoryName).getDefault();
102         //  Socket socket = socketFactory.createSocket();
103         //  SocketAddress localaddr = new InetSocketAddress(localAddress, localPort);
104         //  SocketAddress remoteaddr = new InetSocketAddress(host, port);
105         //  socket.bind(localaddr);
106         //  socket.connect(remoteaddr, timeout);
107         //  return socket;
108         try {
109             Class socketfactoryClass = Class.forName(socketfactoryName);
110             Method method = socketfactoryClass.getMethod("getDefault", 
111                 new Class[] {});
112             Object socketfactory = method.invoke(null, 
113                 new Object[] {});
114             method = socketfactoryClass.getMethod("createSocket", 
115                 new Class[] {});
116             Socket socket = (Socket) method.invoke(socketfactory, new Object[] {});
117             
118             if (INETSOCKETADDRESS_CONSTRUCTOR == null) {
119                 Class addressClass = Class.forName("java.net.InetSocketAddress");
120                 INETSOCKETADDRESS_CONSTRUCTOR = addressClass.getConstructor(
121                     new Class[] { InetAddress.class, Integer.TYPE });
122             }
123                 
124             Object remoteaddr = INETSOCKETADDRESS_CONSTRUCTOR.newInstance(
125                 new Object[] { InetAddress.getByName(host), new Integer(port)});
126 
127             Object localaddr = INETSOCKETADDRESS_CONSTRUCTOR.newInstance(
128                     new Object[] { localAddress, new Integer(localPort)});
129 
130             if (SOCKETCONNECT_METHOD == null) {
131                 SOCKETCONNECT_METHOD = Socket.class.getMethod("connect", 
132                     new Class[] {Class.forName("java.net.SocketAddress"), Integer.TYPE});
133             }
134 
135             if (SOCKETBIND_METHOD == null) {
136                 SOCKETBIND_METHOD = Socket.class.getMethod("bind", 
137                     new Class[] {Class.forName("java.net.SocketAddress")});
138             }
139             SOCKETBIND_METHOD.invoke(socket, new Object[] { localaddr});
140             SOCKETCONNECT_METHOD.invoke(socket, new Object[] { remoteaddr, new Integer(timeout)});
141             return socket;
142         }
143         catch (InvocationTargetException e) {
144             Throwable cause = e.getTargetException(); 
145             if (SOCKETTIMEOUTEXCEPTION_CLASS == null) {
146                 try {
147                     SOCKETTIMEOUTEXCEPTION_CLASS = Class.forName("java.net.SocketTimeoutException");
148                 } catch (ClassNotFoundException ex) {
149                     // At this point this should never happen. Really.
150                     REFLECTION_FAILED = true;
151                     return null;
152                 }
153             }
154             if (SOCKETTIMEOUTEXCEPTION_CLASS.isInstance(cause)) {
155                 throw new ConnectTimeoutException(
156                     "The host did not accept the connection within timeout of " 
157                     + timeout + " ms", cause);
158             }
159             if (cause instanceof IOException) {
160                 throw (IOException)cause;
161             }
162             return null;
163         }
164         catch (Exception e) {
165             REFLECTION_FAILED = true;
166             return null;
167         }
168     }
169 }