KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > management > remote > JMXServiceURL


1 /*
2  * @(#)JMXServiceURL.java 1.29 04/06/10
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8
9 package javax.management.remote;
10
11 import java.io.IOException JavaDoc;
12 import java.io.Serializable JavaDoc;
13 import java.net.InetAddress JavaDoc;
14 import java.net.MalformedURLException JavaDoc;
15 import java.net.UnknownHostException JavaDoc;
16 import java.util.BitSet JavaDoc;
17 import java.util.StringTokenizer JavaDoc;
18
19 import com.sun.jmx.remote.util.ClassLogger;
20 import com.sun.jmx.remote.util.EnvHelp;
21
22 /**
23  * <p>The address of a JMX API connector server. Instances of this class
24  * are immutable.</p>
25  *
26  * <p>The address is an <em>Abstract Service URL</em> for SLP, as
27  * defined in RFC 2609 and amended by RFC 3111. It must look like
28  * this:</p>
29  *
30  * <blockquote>
31  *
32  * <code>service:jmx:<em>protocol</em>:<em>sap</em></code>
33  *
34  * </blockquote>
35  *
36  * <p>Here, <code><em>protocol</em></code> is the transport
37  * protocol to be used to connect to the connector server. It is
38  * a string of one or more ASCII characters, each of which is a
39  * letter, a digit, or one of the characters <code>+</code> or
40  * <code>-</code>. The first character must be a letter.
41  * Uppercase letters are converted into lowercase ones.</p>
42  *
43  * <p><code><em>sap</em></code> is the address at which the connector
44  * server is found. This address uses a subset of the syntax defined
45  * by RFC 2609 for IP-based protocols. It is a subset because the
46  * <code>user@host</code> syntax is not supported.</p>
47  *
48  * <p>The other syntaxes defined by RFC 2609 are not currently
49  * supported by this class.</p>
50  *
51  * <p>The supported syntax is:</p>
52  *
53  * <blockquote>
54  *
55  * <code>//<em>[host[</em>:<em>port]][url-path]</em></code>
56  *
57  * </blockquote>
58  *
59  * <p>Square brackets <code>[]</code> indicate optional parts of
60  * the address. Not all protocols will recognize all optional
61  * parts.</p>
62  *
63  * <p>The <code><em>host</em></code> is a host name, an IPv4 numeric
64  * host address, or an IPv6 numeric address enclosed in square
65  * brackets.</p>
66  *
67  * <p>The <code><em>port</em></code> is a decimal port number. 0
68  * means a default or anonymous port, depending on the protocol.</p>
69  *
70  * <p>The <code><em>host</em></code> and <code><em>port</em></code>
71  * can be omitted. The <code><em>port</em></code> cannot be supplied
72  * without a <code><em>host</em></code>.</p>
73  *
74  * <p>The <code><em>url-path</em></code>, if any, begins with a slash
75  * (<code>/</code>) or a semicolon (<code>;</code>) and continues to
76  * the end of the address. It can contain attributes using the
77  * semicolon syntax specified in RFC 2609. Those attributes are not
78  * parsed by this class and incorrect attribute syntax is not
79  * detected.</p>
80  *
81  * <p>Although it is legal according to RFC 2609 to have a
82  * <code><em>url-path</em></code> that begins with a semicolon, not
83  * all implementations of SLP allow it, so it is recommended to avoid
84  * that syntax.</p>
85  *
86  * <p>Case is not significant in the initial
87  * <code>service:jmx:<em>protocol</em></code> string or in the host
88  * part of the address. Depending on the protocol, case can be
89  * significant in the <code><em>url-path</em></code>.</p>
90  *
91  * @see <a
92  * HREF="ftp://ftp.rfc-editor.org/in-notes/rfc2609.txt">RFC 2609,
93  * "Service Templates and <code>Service:</code> Schemes"</a>
94  * @see <a
95  * HREF="ftp://ftp.rfc-editor.org/in-notes/rfc3111.txt">RFC 3111,
96  * "Service Location Protocol Modifications for IPv6"</a>
97  *
98  * @since 1.5
99  * @since.unbundled 1.0
100  */

101 public class JMXServiceURL implements Serializable JavaDoc {
102
103     private static final long serialVersionUID = 8173364409860779292L;
104
105     /**
106      * <p>Constructs a <code>JMXServiceURL</code> by parsing a Service URL
107      * string.</p>
108      *
109      * @param serviceURL the URL string to be parsed.
110      *
111      * @exception NullPointerException if <code>serviceURL</code> is
112      * null.
113      *
114      * @exception MalformedURLException if <code>serviceURL</code>
115      * does not conform to the syntax for an Abstract Service URL or
116      * if it is not a valid name for a JMX Remote API service. A
117      * <code>JMXServiceURL</code> must begin with the string
118      * <code>"service:jmx:"</code> (case-insensitive). It must not
119      * contain any characters that are not printable ASCII characters.
120      */

121     public JMXServiceURL(String JavaDoc serviceURL) throws MalformedURLException JavaDoc {
122     final int serviceURLLength = serviceURL.length();
123
124     /* Check that there are no non-ASCII characters in the URL,
125        following RFC 2609. */

126     for (int i = 0; i < serviceURLLength; i++) {
127         char c = serviceURL.charAt(i);
128         if (c < 32 || c >= 127) {
129         throw new MalformedURLException JavaDoc("Service URL contains " +
130                         "non-ASCII character 0x" +
131                         Integer.toHexString(c));
132         }
133     }
134
135     // Parse the required prefix
136
final String JavaDoc requiredPrefix = "service:jmx:";
137     final int requiredPrefixLength = requiredPrefix.length();
138     if (!serviceURL.regionMatches(true, // ignore case
139
0, // serviceURL offset
140
requiredPrefix,
141                       0, // requiredPrefix offset
142
requiredPrefixLength)) {
143         throw new MalformedURLException JavaDoc("Service URL must start with " +
144                         requiredPrefix);
145     }
146
147     int[] ptr = new int[1];
148
149     // Parse the protocol name
150
final int protoStart = requiredPrefixLength;
151     final int protoEnd = indexOf(serviceURL, ':', protoStart);
152     this.protocol =
153         serviceURL.substring(protoStart, protoEnd).toLowerCase();
154
155     if (!serviceURL.regionMatches(protoEnd, "://", 0, 3)) {
156         throw new MalformedURLException JavaDoc("Missing \"://\" after " +
157                         "protocol name");
158     }
159
160     // Parse the host name
161
final int hostStart = protoEnd + 3;
162     final int hostEnd;
163     if (hostStart < serviceURLLength
164         && serviceURL.charAt(hostStart) == '[') {
165         hostEnd = serviceURL.indexOf(']', hostStart) + 1;
166         if (hostEnd == 0)
167         throw new MalformedURLException JavaDoc("Bad host name: [ without ]");
168         this.host = serviceURL.substring(hostStart + 1, hostEnd - 1);
169         if (!isNumericIPv6Address(this.host)) {
170         throw new MalformedURLException JavaDoc("Address inside [...] must " +
171                         "be numeric IPv6 address");
172         }
173     } else {
174         hostEnd =
175         indexOfFirstNotInSet(serviceURL, hostNameBitSet, hostStart);
176         this.host = serviceURL.substring(hostStart, hostEnd);
177     }
178
179     // Parse the port number
180
final int portEnd;
181     if (hostEnd < serviceURLLength && serviceURL.charAt(hostEnd) == ':') {
182         if (this.host.length() == 0) {
183         throw new MalformedURLException JavaDoc("Cannot give port number " +
184                         "without host name");
185         }
186         final int portStart = hostEnd + 1;
187         portEnd =
188         indexOfFirstNotInSet(serviceURL, numericBitSet, portStart);
189         final String JavaDoc portString = serviceURL.substring(portStart, portEnd);
190         try {
191         this.port = Integer.parseInt(portString);
192         } catch (NumberFormatException JavaDoc e) {
193         throw new MalformedURLException JavaDoc("Bad port number: \"" +
194                         portString + "\": " + e);
195         }
196     } else {
197         portEnd = hostEnd;
198         this.port = 0;
199     }
200
201     // Parse the URL path
202
final int urlPathStart = portEnd;
203     if (urlPathStart < serviceURLLength)
204         this.urlPath = serviceURL.substring(urlPathStart);
205     else
206         this.urlPath = "";
207
208     validate();
209     }
210
211     /**
212      * <p>Constructs a <code>JMXServiceURL</code> with the given protocol,
213      * host, and port. This constructor is equivalent to
214      * {@link #JMXServiceURL(String, String, int, String)
215      * JMXServiceURL(protocol, host, port, null)}.</p>
216      *
217      * @param protocol the protocol part of the URL. If null, defaults
218      * to <code>jmxmp</code>.
219      *
220      * @param host the host part of the URL. If null, defaults to the
221      * local host name, as determined by
222      * <code>InetAddress.getLocalHost().getHostName()</code>. If it
223      * is a numeric IPv6 address, it can optionally be enclosed in
224      * square brackets <code>[]</code>.
225      *
226      * @param port the port part of the URL.
227      *
228      * @exception MalformedURLException if one of the parts is
229      * syntactically incorrect, or if <code>host</code> is null and it
230      * is not possible to find the local host name, or if
231      * <code>port</code> is negative.
232      */

233     public JMXServiceURL(String JavaDoc protocol, String JavaDoc host, int port)
234         throws MalformedURLException JavaDoc {
235     this(protocol, host, port, null);
236     }
237
238     /**
239      * <p>Constructs a <code>JMXServiceURL</code> with the given parts.
240      *
241      * @param protocol the protocol part of the URL. If null, defaults
242      * to <code>jmxmp</code>.
243      *
244      * @param host the host part of the URL. If null, defaults to the
245      * local host name, as determined by
246      * <code>InetAddress.getLocalHost().getHostName()</code>. If it
247      * is a numeric IPv6 address, it can optionally be enclosed in
248      * square brackets <code>[]</code>.
249      *
250      * @param port the port part of the URL.
251      *
252      * @param urlPath the URL path part of the URL. If null, defaults to
253      * the empty string.
254      *
255      * @exception MalformedURLException if one of the parts is
256      * syntactically incorrect, or if <code>host</code> is null and it
257      * is not possible to find the local host name, or if
258      * <code>port</code> is negative.
259      */

260     public JMXServiceURL(String JavaDoc protocol, String JavaDoc host, int port,
261              String JavaDoc urlPath)
262         throws MalformedURLException JavaDoc {
263     if (protocol == null)
264         protocol = "jmxmp";
265
266     if (host == null) {
267         InetAddress JavaDoc local;
268         try {
269         local = InetAddress.getLocalHost();
270         } catch (UnknownHostException JavaDoc e) {
271         throw new MalformedURLException JavaDoc("Local host name unknown: " +
272                         e);
273         }
274
275         host = local.getHostName();
276
277         /* We might have a hostname that violates DNS naming
278            rules, for example that contains an `_'. While we
279            could be strict and throw an exception, this is rather
280            user-hostile. Instead we use its numerical IP address.
281            We can only reasonably do this for the host==null case.
282            If we're given an explicit host name that is illegal we
283            have to reject it. (Bug 5057532.) */

284         try {
285         validateHost(host);
286         } catch (MalformedURLException JavaDoc e) {
287         if (logger.fineOn()) {
288             logger.fine("JMXServiceURL",
289                 "Replacing illegal local host name " +
290                 host + " with numeric IP address " +
291                 "(see RFC 1034)", e);
292         }
293         host = local.getHostAddress();
294         /* Use the numeric address, which could be either IPv4
295            or IPv6. validateHost will accept either. */

296         }
297     }
298
299     if (host.startsWith("[")) {
300         if (!host.endsWith("]")) {
301         throw new MalformedURLException JavaDoc("Host starts with [ but " +
302                         "does not end with ]");
303         }
304         host = host.substring(1, host.length() - 1);
305         if (!isNumericIPv6Address(host)) {
306         throw new MalformedURLException JavaDoc("Address inside [...] must " +
307                         "be numeric IPv6 address");
308         }
309         if (host.startsWith("["))
310         throw new MalformedURLException JavaDoc("More than one [[...]]");
311     }
312
313     this.protocol = protocol.toLowerCase();
314     this.host = host;
315     this.port = port;
316
317     if (urlPath == null)
318         urlPath = "";
319     this.urlPath = urlPath;
320
321     validate();
322     }
323
324     private void validate() throws MalformedURLException JavaDoc {
325
326     // Check protocol
327

328     final int protoEnd = indexOfFirstNotInSet(protocol, protocolBitSet, 0);
329     if (protoEnd == 0 || protoEnd < protocol.length()
330         || !alphaBitSet.get(protocol.charAt(0))) {
331         throw new MalformedURLException JavaDoc("Missing or invalid protocol " +
332                         "name: \"" + protocol + "\"");
333     }
334
335     // Check host
336

337     validateHost();
338
339     // Check port
340

341     if (port < 0)
342         throw new MalformedURLException JavaDoc("Bad port: " + port);
343
344     // Check URL path
345

346     if (urlPath.length() > 0) {
347         if (!urlPath.startsWith("/") && !urlPath.startsWith(";"))
348         throw new MalformedURLException JavaDoc("Bad URL path: " + urlPath);
349     }
350     }
351
352     private void validateHost() throws MalformedURLException JavaDoc {
353     if (host.length() == 0) {
354         if (port != 0) {
355         throw new MalformedURLException JavaDoc("Cannot give port number " +
356                         "without host name");
357         }
358         return;
359     }
360
361     validateHost(host);
362     }
363
364     private static void validateHost(String JavaDoc h)
365         throws MalformedURLException JavaDoc {
366
367     if (isNumericIPv6Address(h)) {
368         /* We assume J2SE >= 1.4 here. Otherwise you can't
369            use the address anyway. We can't call
370            InetAddress.getByName without checking for a
371            numeric IPv6 address, because we mustn't try to do
372            a DNS lookup in case the address is not actually
373            numeric. */

374         try {
375         InetAddress.getByName(h);
376         } catch (Exception JavaDoc e) {
377         /* We should really catch UnknownHostException
378            here, but a bug in JDK 1.4 causes it to throw
379            ArrayIndexOutOfBoundsException, e.g. if the
380            string is ":". */

381         MalformedURLException JavaDoc bad =
382             new MalformedURLException JavaDoc("Bad IPv6 address: " + h);
383         EnvHelp.initCause(bad, e);
384         throw bad;
385         }
386     } else {
387         /* Tiny state machine to check valid host name. This
388            checks the hostname grammar from RFC 1034 (DNS),
389            page 11. A hostname is a dot-separated list of one
390            or more labels, where each label consists of
391            letters, numbers, or hyphens. A label cannot begin
392            or end with a hyphen. Empty hostnames are not
393            allowed. Note that numeric IPv4 addresses are a
394            special case of this grammar.
395
396            The state is entirely captured by the last
397            character seen, with a virtual `.' preceding the
398            name. We represent any alphanumeric character by
399            `a'.
400         
401            We need a special hack to check, as required by the
402            RFC 2609 (SLP) grammar, that the last component of
403            the hostname begins with a letter. Respecting the
404            intent of the RFC, we only do this if there is more
405            than one component. If your local hostname begins
406            with a digit, we don't reject it. */

407         final int hostLen = h.length();
408         char lastc = '.';
409         boolean sawDot = false;
410         char componentStart = 0;
411
412         loop:
413         for (int i = 0; i < hostLen; i++) {
414         char c = h.charAt(i);
415         boolean isAlphaNumeric = alphaNumericBitSet.get(c);
416         if (lastc == '.')
417             componentStart = c;
418         if (isAlphaNumeric)
419             lastc = 'a';
420         else if (c == '-') {
421             if (lastc == '.')
422             break; // will throw exception
423
lastc = '-';
424         } else if (c == '.') {
425             sawDot = true;
426             if (lastc != 'a')
427             break; // will throw exception
428
lastc = '.';
429         } else {
430             lastc = '.'; // will throw exception
431
break;
432         }
433         }
434
435         try {
436         if (lastc != 'a')
437             throw randomException;
438         if (sawDot && !alphaBitSet.get(componentStart)) {
439             /* Must be a numeric IPv4 address. In addition to
440                the explicitly-thrown exceptions, we can get
441                NoSuchElementException from the calls to
442                tok.nextToken and NumberFormatException from
443                the call to Integer.parseInt. Using exceptions
444                for control flow this way is a bit evil but it
445                does simplify things enormously. */

446             StringTokenizer JavaDoc tok = new StringTokenizer JavaDoc(h, ".", true);
447             for (int i = 0; i < 4; i++) {
448             String JavaDoc ns = tok.nextToken();
449             int n = Integer.parseInt(ns);
450             if (n < 0 || n > 255)
451                 throw randomException;
452             if (i < 3 && !tok.nextToken().equals("."))
453                 throw randomException;
454             }
455             if (tok.hasMoreTokens())
456             throw randomException;
457         }
458         } catch (Exception JavaDoc e) {
459         throw new MalformedURLException JavaDoc("Bad host: \"" + h + "\"");
460         }
461     }
462     }
463
464     private static final Exception JavaDoc randomException = new Exception JavaDoc();
465
466
467     /**
468      * <p>The protocol part of the Service URL.
469      *
470      * @return the protocol part of the Service URL. This is never null.
471      */

472     public String JavaDoc getProtocol() {
473     return protocol;
474     }
475
476     /**
477      * <p>The host part of the Service URL. If the Service URL was
478      * constructed with the constructor that takes a URL string
479      * parameter, the result is the substring specifying the host in
480      * that URL. If the Service URL was constructed with a
481      * constructor that takes a separate host parameter, the result is
482      * the string that was specified. If that string was null, the
483      * result is
484      * <code>InetAddress.getLocalHost().getHostName()</code>.</p>
485      *
486      * <p>In either case, if the host was specified using the
487      * <code>[...]</code> syntax for numeric IPv6 addresses, the
488      * square brackets are not included in the return value here.</p>
489      *
490      * @return the host part of the Service URL. This is never null.
491      */

492     public String JavaDoc getHost() {
493     return host;
494     }
495
496     /**
497      * <p>The port of the Service URL. If no port was
498      * specified, the returned value is 0.</p>
499      *
500      * @return the port of the Service URL, or 0 if none.
501      */

502     public int getPort() {
503     return port;
504     }
505
506     /**
507      * <p>The URL Path part of the Service URL. This is an empty
508      * string, or a string beginning with a slash (<code>/</code>), or
509      * a string beginning with a semicolon (<code>;</code>).
510      *
511      * @return the URL Path part of the Service URL. This is never
512      * null.
513      */

514     public String JavaDoc getURLPath() {
515     return urlPath;
516     }
517
518     /**
519      * <p>The string representation of this Service URL. If the value
520      * returned by this method is supplied to the
521      * <code>JMXServiceURL</code> constructor, the resultant object is
522      * equal to this one.</p>
523      *
524      * <p>The <code><em>host</em></code> part of the returned string
525      * is the value returned by {@link #getHost()}. If that value
526      * specifies a numeric IPv6 address, it is surrounded by square
527      * brackets <code>[]</code>.</p>
528      *
529      * <p>The <code><em>port</em></code> part of the returned string
530      * is the value returned by {@link #getPort()} in its shortest
531      * decimal form. If the value is zero, it is omitted.</p>
532      *
533      * @return the string representation of this Service URL.
534      */

535     public String JavaDoc toString() {
536     /* We don't bother synchronizing the access to toString. At worst,
537        n threads will independently compute and store the same value. */

538     if (toString != null)
539         return toString;
540     StringBuffer JavaDoc buf = new StringBuffer JavaDoc("service:jmx:");
541     buf.append(getProtocol()).append("://");
542     final String JavaDoc getHost = getHost();
543     if (isNumericIPv6Address(getHost))
544         buf.append('[').append(getHost).append(']');
545     else
546         buf.append(getHost);
547     final int getPort = getPort();
548     if (getPort != 0)
549         buf.append(':').append(getPort);
550     buf.append(getURLPath());
551     toString = buf.toString();
552     return toString;
553     }
554
555     /**
556      * <p>Indicates whether some other object is equal to this one.
557      * This method returns true if and only if <code>obj</code> is an
558      * instance of <code>JMXServiceURL</code> whose {@link
559      * #getProtocol()}, {@link #getHost()}, {@link #getPort()}, and
560      * {@link #getURLPath()} methods return the same values as for
561      * this object. The values for {@link #getProtocol()} and {@link
562      * #getHost()} can differ in case without affecting equality.
563      *
564      * @param obj the reference object with which to compare.
565      *
566      * @return <code>true</code> if this object is the same as the
567      * <code>obj</code> argument; <code>false</code> otherwise.
568      */

569     public boolean equals(Object JavaDoc obj) {
570     if (!(obj instanceof JMXServiceURL JavaDoc))
571         return false;
572     JMXServiceURL JavaDoc u = (JMXServiceURL JavaDoc) obj;
573     return
574         (u.getProtocol().equalsIgnoreCase(getProtocol()) &&
575          u.getHost().equalsIgnoreCase(getHost()) &&
576          u.getPort() == getPort() &&
577          u.getURLPath().equals(getURLPath()));
578     }
579
580     public int hashCode() {
581     return toString().hashCode();
582     }
583
584     /* True if this string, assumed to be a valid argument to
585      * InetAddress.getByName, is a numeric IPv6 address.
586      */

587     private static boolean isNumericIPv6Address(String JavaDoc s) {
588     // address contains colon iff it's a numeric IPv6 address
589
return (s.indexOf(':') >= 0);
590     }
591
592     // like String.indexOf but returns string length not -1 if not present
593
private static int indexOf(String JavaDoc s, char c, int fromIndex) {
594     int index = s.indexOf(c, fromIndex);
595     if (index < 0)
596         return s.length();
597     else
598         return index;
599     }
600
601     private static int indexOfFirstNotInSet(String JavaDoc s, BitSet JavaDoc set,
602                         int fromIndex) {
603     final int slen = s.length();
604     int i = fromIndex;
605     while (true) {
606         if (i >= slen)
607         break;
608         char c = s.charAt(i);
609         if (c >= 128)
610         break; // not ASCII
611
if (!set.get(c))
612         break;
613         i++;
614     }
615     return i;
616     }
617
618     private final static BitSet JavaDoc alphaBitSet = new BitSet JavaDoc(128);
619     private final static BitSet JavaDoc numericBitSet = new BitSet JavaDoc(128);
620     private final static BitSet JavaDoc alphaNumericBitSet = new BitSet JavaDoc(128);
621     private final static BitSet JavaDoc protocolBitSet = new BitSet JavaDoc(128);
622     private final static BitSet JavaDoc hostNameBitSet = new BitSet JavaDoc(128);
623     static {
624     /* J2SE 1.4 adds lots of handy methods to BitSet that would
625        allow us to simplify here, e.g. by not writing loops, but
626        we want to work on J2SE 1.3 too. */

627
628     for (char c = '0'; c <= '9'; c++)
629         numericBitSet.set(c);
630
631     for (char c = 'A'; c <= 'Z'; c++)
632         alphaBitSet.set(c);
633     for (char c = 'a'; c <= 'z'; c++)
634         alphaBitSet.set(c);
635
636     alphaNumericBitSet.or(alphaBitSet);
637     alphaNumericBitSet.or(numericBitSet);
638
639     protocolBitSet.or(alphaNumericBitSet);
640     protocolBitSet.set('+');
641     protocolBitSet.set('-');
642
643     hostNameBitSet.or(alphaNumericBitSet);
644     hostNameBitSet.set('-');
645     hostNameBitSet.set('.');
646     }
647
648     private static void addCharsToBitSet(BitSet JavaDoc set, String JavaDoc chars) {
649     for (int i = 0; i < chars.length(); i++)
650         set.set(chars.charAt(i));
651     }
652
653     /**
654      * The value returned by {@link #getProtocol()}.
655      */

656     private final String JavaDoc protocol;
657
658     /**
659      * The value returned by {@link #getHost()}.
660      */

661     private final String JavaDoc host;
662
663     /**
664      * The value returned by {@link #getPort()}.
665      */

666     private final int port;
667
668     /**
669      * The value returned by {@link #getURLPath()}.
670      */

671     private final String JavaDoc urlPath;
672
673     /**
674      * Cached result of {@link #toString()}.
675      */

676     private transient String JavaDoc toString;
677
678     private static final ClassLogger logger =
679     new ClassLogger("javax.management.remote.misc", "JMXServiceURL");
680 }
681
Popular Tags