2
* JBoss, Home of Professional Open Source
3
* Copyright 2005, JBoss Inc., and individual contributors as indicated
4
* by the @authors tag. See the copyright.txt in the distribution for a
5
* full listing of individual contributors.
7
* This is free software; you can redistribute it and/or modify it
8
* under the terms of the GNU Lesser General Public License as
9
* published by the Free Software Foundation; either version 2.1 of
10
* the License, or (at your option) any later version.
12
* This software is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
* Lesser General Public License for more details.
17
* You should have received a copy of the GNU Lesser General Public
18
* License along with this software; if not, write to the Free
19
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23
package org.jboss.remoting;
25
import java.io.Serializable;
26
import java.net.InetAddress;
27
import java.net.MalformedURLException;
29
import java.net.URISyntaxException;
30
import java.net.UnknownHostException;
31
import java.security.AccessController;
32
import java.security.PrivilegedActionException;
33
import java.security.PrivilegedExceptionAction;
34
import java.util.ArrayList;
35
import java.util.Collection;
36
import java.util.Iterator;
37
import java.util.List;
39
import java.util.StringTokenizer;
40
import java.util.TreeMap;
42
import org.jboss.logging.Logger;
43
import org.jboss.remoting.serialization.SerializationStreamFactory;
44
import org.jboss.remoting.transport.ClientInvoker;
45
import org.jboss.remoting.util.SecurityUtility;
48
* InvokerLocator is an object that indentifies a specific Invoker on the network, via a unique
49
* locator URI. The locator URI is in the format: <P>
51
* <tt>protocol://host[:port][/path[?param=value¶m2=value2]]</tt> <P>
53
* For example, a http based locator might be: <P>
55
* <tt>http://192.168.10.1:8081</tt> <P>
57
* An example Socket based locator might be: <P>
59
* <tt>socket://192.168.10.1:9999</tt> <P>
61
* An example RMI based locator might be: <P>
63
* <tt>rmi://localhost</tt> <P>
65
* NOTE: If no hostname is given (e.g., "socket://:5555"), then the hostname will
66
* automatically be resolved to the outside IP address of the local machine.
67
* If the given hostname is 0.0.0.0 and the system
68
* property "jboss.bind.address" is set, then the hostname will be replaced by the value
69
* associated with 'jboss.bind.address".
71
* @author <a href="mailto:jhaynie@vocalocity.net">Jeff Haynie</a>
72
* @author <a href="mailto:telrod@e2technologies.net">Tom Elrod</a>
73
* @version $Revision: 4993 $
75
public class InvokerLocator implements Serializable
77
static final long serialVersionUID;
78
protected static Logger log = Logger.getLogger(InvokerLocator.class);
79
protected static Boolean legacyParsingFlag;
81
protected String protocol;
82
protected String host;
83
protected ArrayList connectHomes = new ArrayList();
84
protected ArrayList homes = new ArrayList();
86
protected String path;
87
protected String query;
88
protected Map parameters;
90
private String originalURL;
91
private Home homeInUse;
96
if(Version.getDefaultVersion() == Version.VERSION_1)
98
serialVersionUID = -2909329895029296248L;
102
serialVersionUID = -4977622166779282521L;
106
public static boolean getUseLegacyParsing()
108
if (legacyParsingFlag == null)
110
return legacyParsingFlag.booleanValue();
113
public static void setUseLegacyParsing(boolean flag)
115
legacyParsingFlag = new Boolean(flag);
119
* Indicates should address binding to all network interfaces (i.e. 0.0.0.0)
121
public static final String ANY = "0.0.0.0";
123
* Constant value for server bind address system property. Value is 'jboss.bind.address'.
125
private static final String SERVER_BIND_ADDRESS = "jboss.bind.address";
128
* Public key to use when want to specify that locator look up local address by
131
public static final String BIND_BY_HOST = "remoting.bind_by_host";
135
* Constant to define the param name to be used when defining the data type.
137
public static final String DATATYPE = "datatype";
138
public static final String DATATYPE_CASED = "dataType";
141
* Constant to define the param name to be used when defining the serialization type.
143
public static final String SERIALIZATIONTYPE = "serializationtype";
144
public static final String SERIALIZATIONTYPE_CASED = "serializationType";
147
* Constant to define the param name to be used when defining the marshaller fully qualified classname
149
public static final String MARSHALLER = "marshaller";
151
* Constant to define the param name to be used when defining the unmarshaller fully qualified classname
153
public static final String UNMARSHALLER = "unmarshaller";
156
* Constant to define what port the marshalling loader port resides on.
158
public static final String LOADER_PORT = "loaderport";
161
* Constant to define the param name to be used when defining if marshalling should be by value,
162
* which means will be using local client invoker with cloning of payload value.
164
public static final String BYVALUE = "byvalue";
167
* Constant to define the param name to be used when defining if marshalling should use
168
* remote client invoker instead of using local client invoker (even though both client and
169
* server invokers are within same JVM).
171
public static final String FORCE_REMOTE = "force_remote";
174
* Constant to define if client should try to automatically establish a
175
* lease with the server. Value for this parameter key should be either 'true' or 'false'.
177
public static final String CLIENT_LEASE = "leasing";
180
* Constant to define what the client lease period should be in the case that
181
* server side leasing is turned on. Value for this parameter key should be the number
182
* of milliseconds to wait before each client lease renewal and must be greater than zero
183
* in order to be recognized.
185
public static final String CLIENT_LEASE_PERIOD = "lease_period";
189
* Constant to define if InvokerLocator should use the old (ad hoc) parsing
190
* algorithm instead of the new, URI based, parsing algorithm.
192
public static final String LEGACY_PARSING = "legacyParsing";
195
* Serves as placeholder in host position when multiple hosts are given in the
196
* query part by way of the parameter "hosts". E.g.
198
* socket://multihome:8888/?hosts=host1.jboss.org:host2.jboss.org
200
public static final String MULTIHOME = "multihome";
203
* Parameter key used for specifying multiple homes to connect to. E.g.
205
* socket://multihome/?connecthomes=host1.jboss.org:7777!host2.jboss.org:8888
207
public static final String CONNECT_HOMES_KEY = "connecthomes";
210
* Parameter key used for specifying multiple binding homes. E.g.
212
* socket://multihome/?homes=a.org:66!b.org:77&homes=c.org:88!d.org:99
214
public static final String HOMES_KEY = "homes";
217
* Parameter key used for specifying default connecting port for
218
* multihome InvokerLocator.
220
public static final String DEFAULT_CONNECT_PORT = "defaultConnectPort";
223
* Parameter key used for specifying default server bind port for
224
* multihme InvokerLocator.
226
public static final String DEFAULT_PORT = "defaultPort";
230
* InvokerLocator leaves address 0.0.0.0 unchanged. Once serverBindAddress has been
231
* extracted from the InvokerLocator, it is necessary to transform 0.0.0.0 into an
232
* address that contacted over the network. See JBREM-687.
234
public static InvokerLocator validateLocator(InvokerLocator locator) throws MalformedURLException
236
InvokerLocator externalLocator = locator;
238
String host = locator.getHost();
239
String newHost = null;
240
if(host == null || InvokerLocator.ANY.equals(host))
242
// now need to get some external bindable address
245
newHost = (String)AccessController.doPrivileged( new PrivilegedExceptionAction()
247
public Object run() throws Exception
249
String bindByHost = System.getProperty(InvokerLocator.BIND_BY_HOST, "True");
250
boolean byHost = Boolean.valueOf(bindByHost).booleanValue();
253
return InetAddress.getLocalHost().getHostName();
257
return InetAddress.getLocalHost().getHostAddress();
262
catch (PrivilegedActionException e)
264
log.debug("Could not get host by name or address.", e.getCause());
269
// now what? step through network interfaces?
270
throw new RuntimeException("Can not determine bindable address for locator (" + locator + ")");
273
externalLocator = new InvokerLocator(locator.protocol, newHost, locator.port,
274
locator.getPath(), locator.getParameters());
277
return externalLocator;
281
public static void extractHomes(String homeList, List list, int defaultPort)
283
StringTokenizer tok = new StringTokenizer(homeList, "!");
284
while(tok.hasMoreTokens())
288
String token = tok.nextToken();
289
boolean isIPv6 = token.indexOf('[') >= 0;
294
int pos = token.indexOf(']');
295
if (pos + 1 == token.length())
298
boundary = token.indexOf(']') + 1;
303
boundary = token.lastIndexOf(':');
308
h = token.substring(0, boundary);
312
p = Integer.valueOf(token.substring(boundary + 1)).intValue();
314
catch (NumberFormatException e)
316
log.warn("invalid port value: " + token.substring(boundary + 1));
327
list.add(new Home(h, p));
332
public static String convertHomesListToString(List homes)
334
if (homes == null || homes.size() == 0)
337
Iterator it = homes.iterator();
338
StringBuffer b = new StringBuffer(((Home) it.next()).toString());
341
b.append("!").append(it.next());
348
* Constructs the object used to identify a remoting server via simple uri format string (e.g. socket://myhost:7000).
349
* Note: the uri passed may not always be the one returned via call to getLocatorURI() as may need to change if
350
* port not specified, host is 0.0.0.0, etc. If need original uri that is passed to this constructor, need to
351
* call getOriginalURI().
353
* @throws MalformedURLException
355
public InvokerLocator(String uri)
356
throws MalformedURLException
363
private void parse(String uriString) throws MalformedURLException
365
boolean doLegacyParsing = false;
366
if (legacyParsingFlag != null)
368
doLegacyParsing = legacyParsingFlag.booleanValue();
372
String s = getSystemProperty(LEGACY_PARSING);
373
doLegacyParsing = "true".equalsIgnoreCase(s);
378
log.warn("using deprecated legacy URL parsing routine");
379
legacyParse(uriString);
388
StringTokenizer tok = new StringTokenizer(query, "&");
389
parameters = new TreeMap();
390
while(tok.hasMoreTokens())
392
String token = tok.nextToken();
393
int eq = token.indexOf("=");
394
String name = (eq > -1) ? token.substring(0, eq) : token;
395
String value = (eq > -1) ? token.substring(eq + 1) : "";
396
parameters.put(name, value);
400
if (!MULTIHOME.equals(host) && parameters != null)
402
// Use "host:port" to connect.
403
String s = (String) parameters.remove(CONNECT_HOMES_KEY);
404
if (s != null) log.warn("host != " + MULTIHOME + ": " + CONNECT_HOMES_KEY + " will be ignored");
407
if (parameters != null)
409
String homesString = (String) parameters.remove(HOMES_KEY);
410
String connectHomesString = (String) parameters.remove(CONNECT_HOMES_KEY);
411
createHomeLists(parameters, homesString, connectHomesString);
414
// rebuild it, since the host probably got resolved and the port changed
417
if (!MULTIHOME.equals(host))
419
homeInUse = new Home(host, port);
420
connectHomes.add(homeInUse);
422
homes.add(homeInUse);
426
private void URIParse(String uriString) throws MalformedURLException
430
URI uri = new URI(encodePercent(uriString));
431
protocol = uri.getScheme();
432
host = decodePercent(resolveHost(uri.getHost()));
433
port = uri.getPort();
434
path = uri.getPath();
435
query = decodePercent(uri.getQuery());
437
catch (URISyntaxException e)
439
throw new MalformedURLException(e.getMessage());
443
private void legacyParse(String uri) throws MalformedURLException
445
log.warn("Legacy InvokerLocator parsing is deprecated");
446
int i = uri.indexOf("://");
449
throw new MalformedURLException("Invalid url " + uri);
451
String tmp = uri.substring(i + 3);
452
this.protocol = uri.substring(0, i);
453
i = tmp.indexOf("/");
454
int p = tmp.indexOf(":");
457
p = (p < i ? p : -1);
461
host = resolveHost(tmp.substring(0, p).trim());
464
port = Integer.parseInt(tmp.substring(p + 1, i));
468
port = Integer.parseInt(tmp.substring(p + 1));
475
host = resolveHost(tmp.substring(0, i).trim());
479
host = resolveHost(tmp.substring(0).trim());
484
// now look for any path
485
p = tmp.indexOf("?");
488
path = tmp.substring(i + 1, p);
489
query = tmp.substring(p + 1);
493
p = tmp.indexOf("/");
496
path = tmp.substring(p + 1);
506
private static final String resolveHost(String host)
510
host = fixRemoteAddress(host);
512
else if(host.indexOf("0.0.0.0") != -1)
514
String bindAddress = getSystemProperty(SERVER_BIND_ADDRESS, "0.0.0.0");
515
if(bindAddress.equals("0.0.0.0"))
517
host = fixRemoteAddress(host);
521
host = host.replaceAll("0\\.0\\.0\\.0", getSystemProperty(SERVER_BIND_ADDRESS));
527
private static String fixRemoteAddress(String address)
533
address = (String)AccessController.doPrivileged( new PrivilegedExceptionAction()
535
public Object run() throws UnknownHostException
537
String bindByHost = System.getProperty(BIND_BY_HOST, "True");
538
boolean byHost = Boolean.valueOf(bindByHost).booleanValue();
542
return InetAddress.getLocalHost().getHostName();
546
return InetAddress.getLocalHost().getHostAddress();
551
catch (PrivilegedActionException e)
553
log.debug("error", e.getCause());
561
private void createHomeLists(Map parameters, String homesString, String connectHomesString)
563
// DEFAULT_PORT value takes precedence, followed by port value.
564
int defaultPort = port;
565
String s = (String) parameters.get(DEFAULT_PORT);
566
if (s != null && s != "")
570
defaultPort = Integer.parseInt(s);
574
log.warn("invalid value for " + DEFAULT_PORT + ": " + s);
578
if (homesString != null)
580
extractHomes(homesString, homes, defaultPort);
583
// DEFAULT_CONNECT_PORT value takes precedence, followed by
584
// DEFAULT_PORT value and then port value.
585
s = (String) parameters.get(DEFAULT_CONNECT_PORT);
586
if (s != null && s != "")
590
defaultPort = Integer.parseInt(s);
594
log.warn("invalid value for " + DEFAULT_CONNECT_PORT + ": " + s);
598
if (connectHomesString != null)
600
extractHomes(connectHomesString, connectHomes, defaultPort);
606
* Constructs the object used to identify a remoting server.
613
public InvokerLocator(String protocol, String host, int port, String path, Map parameters)
615
this.protocol = protocol;
616
this.host = resolveHost(host);
618
this.path = path == null ? "" : path;
619
this.parameters = parameters == null ? new TreeMap() : new TreeMap(parameters);
621
if (this.parameters != null)
623
String homesString = (String) this.parameters.remove(HOMES_KEY);
624
String connectHomesString = (String) this.parameters.remove(CONNECT_HOMES_KEY);
625
createHomeLists(this.parameters, homesString, connectHomesString);
631
if (!MULTIHOME.equals(host))
633
homeInUse = new Home(host, port);
634
homes.add(homeInUse);
635
connectHomes.add(homeInUse);
637
if (parameters != null)
639
// Use "host:port" to connect.
640
String s = (String) parameters.remove(CONNECT_HOMES_KEY);
641
if (s != null) log.warn("host != " + MULTIHOME + ": " + CONNECT_HOMES_KEY + " will be ignored");
646
public int hashCode()
648
return uri.hashCode();
652
* Compares to see if Object passed is of type InvokerLocator and
653
* it's internal locator uri hashcode is same as this one. Note, this
654
* means is testing to see if not only the host, protocol, and port are the
655
* same, but the path and parameters as well. Therefore 'socket://localhost:9000'
656
* and 'socket://localhost:9000/foobar' would NOT be considered equal.
660
public boolean equals(Object obj)
662
return obj != null && obj instanceof InvokerLocator && uri.equals(((InvokerLocator)obj).getLocatorURI());
666
* Compares to see if InvokerLocator passed represents the same physical remoting server
667
* endpoint as this one. Unlike the equals() method, this just tests to see if protocol, host,
668
* and port are the same and ignores the path and parameters.
672
public boolean isSameEndpoint(InvokerLocator compareMe)
674
return compareMe != null && getProtocol().equalsIgnoreCase(compareMe.getProtocol()) &&
675
getHost().equalsIgnoreCase(compareMe.getHost()) && getPort() == compareMe.getPort();
678
public boolean isCompatibleWith(InvokerLocator other)
683
// If this or other comes from pre-2.4.0 Remoting.
684
if (homes == null || other.homes == null)
687
boolean result1 = getProtocol().equalsIgnoreCase(other.getProtocol()) &&
688
path.equals(other.getPath()) &&
689
parameters.equals(other.getParameters());
691
ArrayList tempHomes = connectHomes.isEmpty() ? homes : connectHomes;
692
boolean result2 = intersects(tempHomes, other.getConnectHomeList()) ||
693
intersects(tempHomes, other.getHomeList());
695
return result1 && result2;
699
* return the locator URI, in the format: <P>
701
* <tt>protocol://host[:port][/path[?param=value¶m2=value2]]</tt>
702
* Note, this may not be the same as the original uri passed as parameter to the constructor.
705
public String getLocatorURI()
710
public String getProtocol()
715
public String getHost()
717
if (host.equals(MULTIHOME) && homeInUse != null)
718
return homeInUse.host;
723
public String getActualHost()
728
public boolean isMultihome()
730
return MULTIHOME.equals(host);
733
public String getConnectHomes()
735
return convertHomesListToString(connectHomes);
738
public List getConnectHomeList()
740
if (connectHomes == null)
742
ArrayList list = new ArrayList();
743
list.add(new Home(host, port));
747
return new ArrayList(connectHomes);
750
public void setConnectHomeList(List connectHomes)
752
if (connectHomes == null)
753
this.connectHomes = new ArrayList();
755
this.connectHomes = new ArrayList(connectHomes);
760
public Home getHomeInUse()
762
if (homeInUse != null || isMultihome())
765
return new Home(host, port);
768
public void setHomeInUse(Home homeInUse)
770
this.homeInUse = homeInUse;
773
public String getHomes()
775
return convertHomesListToString(homes);
778
public List getHomeList()
782
ArrayList list = new ArrayList();
783
list.add(new Home(host, port));
784
return new ArrayList();
787
return new ArrayList(homes);
790
public void setHomeList(List homes)
793
this.homes = new ArrayList();
795
this.homes = new ArrayList(homes);
802
if (host.equals(MULTIHOME) && homeInUse != null)
803
return homeInUse.port;
808
public int getActualPort()
813
public String getPath()
818
public Map getParameters()
820
if (parameters == null)
822
parameters = new TreeMap();
827
public String toString()
829
return "InvokerLocator [" + uri + "]";
833
* Gets the original uri passed to constructor (if there was one).
836
public String getOriginalURI()
842
* narrow this invoker to a specific RemoteClientInvoker instance
847
public ClientInvoker narrow() throws Exception
849
return InvokerRegistry.createClientInvoker(this);
853
public String findSerializationType()
855
String serializationTypeLocal = SerializationStreamFactory.JAVA;
856
if(parameters != null)
858
serializationTypeLocal = (String) parameters.get(SERIALIZATIONTYPE);
859
if(serializationTypeLocal == null)
861
serializationTypeLocal = (String) parameters.get(InvokerLocator.SERIALIZATIONTYPE_CASED);
862
if(serializationTypeLocal == null)
864
serializationTypeLocal = SerializationStreamFactory.JAVA;
869
return serializationTypeLocal;
872
protected boolean intersects(Collection c1, Collection c2)
874
Iterator it = c1.iterator();
877
if (c2.contains(it.next()))
883
protected void rebuildLocatorURI()
885
String portPart = (port > -1) ? (":" + port) : "";
886
String divider = path.startsWith("/") ? "" : "/";
887
String parametersPart = (parameters != null) ? "?" : "";
888
uri = protocol + "://" + host + portPart + divider + path + parametersPart;
890
if(parameters != null)
892
if (!homes.isEmpty())
893
parameters.put(HOMES_KEY, convertHomesListToString(homes));
894
if (!connectHomes.isEmpty())
895
parameters.put(CONNECT_HOMES_KEY, convertHomesListToString(connectHomes));
897
Iterator iter = parameters.keySet().iterator();
898
while(iter.hasNext())
900
String key = (String) iter.next();
901
String val = (String) parameters.get(key);
902
uri += key + "=" + val;
909
parameters.remove(HOMES_KEY);
910
parameters.remove(CONNECT_HOMES_KEY);
914
protected static String encodePercent(String s)
916
if (s == null) return null;
917
StringTokenizer st = new StringTokenizer(s, "%");
918
StringBuffer sb = new StringBuffer();
919
int limit = st.countTokens() - 1;
920
for (int i = 0; i < limit; i++)
922
String token = st.nextToken();
923
sb.append(token).append("%25");
925
sb.append(st.nextToken());
926
return sb.toString();
929
protected static String decodePercent(String s)
931
if (s == null) return null;
932
StringBuffer sb = new StringBuffer();
934
int index = s.indexOf("%25", fromIndex);
937
sb.append(s.substring(fromIndex, index)).append('%');
938
fromIndex = index + 3;
939
index = s.indexOf("%25", fromIndex);
941
sb.append(s.substring(fromIndex));
942
return sb.toString();
945
static private String getSystemProperty(final String name, final String defaultValue)
947
if (SecurityUtility.skipAccessControl())
948
return System.getProperty(name, defaultValue);
953
value = (String)AccessController.doPrivileged( new PrivilegedExceptionAction()
955
public Object run() throws Exception
957
return System.getProperty(name, defaultValue);
961
catch (PrivilegedActionException e)
963
throw (RuntimeException) e.getCause();
969
static private String getSystemProperty(final String name)
971
if (SecurityUtility.skipAccessControl())
972
return System.getProperty(name);
977
value = (String)AccessController.doPrivileged( new PrivilegedExceptionAction()
979
public Object run() throws Exception
981
return System.getProperty(name);
985
catch (PrivilegedActionException e)
987
throw (RuntimeException) e.getCause();