View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    * 
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   * 
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.river.discovery;
20  
21  import java.io.IOException;
22  import java.net.InetAddress;
23  import java.net.InetSocketAddress;
24  import java.net.Socket;
25  import java.net.SocketException;
26  import java.net.SocketTimeoutException;
27  import java.net.UnknownHostException;
28  import net.jini.core.constraint.InvocationConstraints;
29  
30  /**
31   * Utility class used by implementations which want to perform unicast
32   * discovery on possibly multiple IP addresses for a given host name.
33   * This class supports unicast discovery constraints as specified in
34   * DiscoveryConstraints.
35   */
36  public abstract class MultiIPDiscovery {
37      // Default value for unicast socket timeout
38      public final static int DEFAULT_TIMEOUT = 60 * 1000;
39  
40      public UnicastResponse getResponse(String scheme, String host, int port, InvocationConstraints constraints)
41  	throws IOException, ClassNotFoundException
42      {
43  	InetAddress addrs[] = null;
44  	try {
45  	    addrs = InetAddress.getAllByName(host);
46  	} catch (UnknownHostException uhe) {
47  	    // Name resolution failed.
48  	    // We'll just try to use the host name later anyway.
49  	}
50  	
51  	DiscoveryConstraints dc = DiscoveryConstraints.process(constraints);
52  	int pv = dc.chooseProtocolVersion();
53  	Discovery disco;
54          /*
55           * https is not a jini discovery protocol, it is the https protocol,
56           * therefore we cannot perform a handshake to select the discovery
57           * provider, or the version.  This is only provided to allow
58           * Unicast Discovery to transit through a https proxy server or firewall.
59           */
60          if ("https".equals(scheme)){
61              disco = Discovery.getUnicastHttps(null);
62          } else {
63              switch (pv) {
64                  case Discovery.PROTOCOL_VERSION_1:
65                      disco = Discovery.getProtocol1();
66                      break;
67                  case Discovery.PROTOCOL_VERSION_2:
68                      disco = Discovery.getProtocol2(null);
69                      break;
70                  default:
71                      throw new AssertionError(pv);
72              }
73          }
74  
75  	long deadline = dc.getConnectionDeadline(Long.MAX_VALUE);
76  	long connectionTimeout = getTimeout(deadline);
77  
78  	if (addrs == null) {
79  	    return getSingleResponse(host, connectionTimeout, port, dc, disco);
80  	}
81  	
82  	IOException ioEx = null;
83  	SecurityException secEx = null;
84  	ClassNotFoundException cnfEx = null;
85  	for (int i = 0, l = addrs.length; i < l; i++) {
86  	    try {
87  		return getSingleResponse(addrs[i].getHostAddress(),
88  				     connectionTimeout, port, dc, disco);
89  	    } catch (ClassNotFoundException ex) {
90  		cnfEx = ex;
91  		singleResponseException(ex, addrs[i], port);
92  	    } catch (IOException ex) {
93  		ioEx = ex;
94  		singleResponseException(ex, addrs[i], port);
95  	    } catch (SecurityException ex) {
96  		secEx = ex;
97  		singleResponseException(ex, addrs[i], port);
98  	    }
99  	    
100 	    try {
101 		connectionTimeout = getTimeout(deadline);
102 	    } catch (SocketTimeoutException ex) {
103 		if (ioEx == null) {
104 		    ioEx = ex;
105 		}
106 		// Out of time.
107 		break;
108 	    }
109 	}
110 	if (cnfEx != null) {
111 	    throw cnfEx;
112 	}
113 	if (ioEx != null) {
114 	    throw ioEx;
115 	}
116 	assert (secEx != null);
117 	throw secEx;
118     }
119     
120     private long getTimeout(long deadline) throws SocketTimeoutException {
121 	long now = System.currentTimeMillis();
122 	if (now >= deadline) {
123 	    throw new SocketTimeoutException("timeout expired before"
124 					     + " connection attempted");
125 	}
126 	return deadline - now;
127     }
128     
129     private UnicastResponse getSingleResponse(String host,
130 					      long connectionTimeout,
131 					      int port,
132 					      DiscoveryConstraints dc,
133 					      Discovery disco)
134 	throws IOException, ClassNotFoundException
135     {
136 	Socket s = new Socket();
137         boolean discoveryAttempted = false;
138         try {
139             if (connectionTimeout > Integer.MAX_VALUE) {
140                 s.connect(new InetSocketAddress(host, port));
141             } else {
142                 s.connect(new InetSocketAddress(host, port), 
143                           (int) connectionTimeout);
144             }
145             try {
146                 s.setTcpNoDelay(true);
147             } catch (SocketException e) {
148                 // ignore possible failures and proceed anyway
149             }
150             try {
151                 s.setKeepAlive(true);
152             } catch (SocketException e) {
153                 // ignore possible failures and proceed anyway
154             }
155             s.setSoTimeout(dc.getUnicastSocketTimeout(
156                             getDefaultUnicastSocketTimeout()));
157             discoveryAttempted = true;
158             return performDiscovery(disco, dc, s);
159         } finally {
160             try {
161                 s.close();
162             } catch (IOException e) {
163                 if (discoveryAttempted) {
164                     socketCloseException(e);
165                 }
166             }
167         }
168     }
169     
170     /*
171      * Subclasses may override this method to supply their own default
172      * timeout. This class implements this method to return a value of
173      * DEFAULT_TIMEOUT.
174      */
175     protected int getDefaultUnicastSocketTimeout() {
176 	return DEFAULT_TIMEOUT;
177     }
178     
179     /*
180      * Called when doing unicast discovery on a single IP results in a
181      * ClassNotFoundException, IOException or SecurityException. The subclass
182      * may perform any action it pleases, like logging. This class implements
183      * this method to by default do nothing.
184      */
185     protected void singleResponseException(Exception ex,
186 					   InetAddress addr,
187 					   int port)
188     { // do nothing
189     }
190     
191     /*
192      * Called when close of a socket on which discovery has been attempted
193      * fails. This class implements this method to do nothing by default.
194      */
195     protected void socketCloseException(IOException ex) {}
196     
197     /*
198      * Called to actually perform the discovery operation. All other protected
199      * methods have default implementations, this one must be implemented.
200      */
201     protected abstract UnicastResponse performDiscovery(Discovery disco,
202 							DiscoveryConstraints dc,
203 							Socket s)
204 	throws IOException, ClassNotFoundException;
205 	    
206 }