1 /*
2  * Copyright (c) 2004, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package sun.jvmstat.monitor;
27 
28 import java.net.*;
29 
30 /**
31  * An abstraction that identifies a target host and communications
32  * protocol. The HostIdentifier, or hostid, provides a convenient string
33  * representation of the information needed to locate and communicate with
34  * a target host. The string, based on a {@link URI}, may specify the
35  * the communications protocol, host name, and protocol specific information
36  * for a target host. The format for a HostIdentifier string is:
37  * <pre>
38  *       [<I>protocol</I>:][[<I>//</I>]<I>hostname</I>][<I>:port</I>][<I>/servername</I>]
39  * </pre>
40  * There are actually no required components of this string, as a null string
41  * is interpreted to mean a local connection to the local host and is equivalent
42  * to the string <em>local://localhost</em>. The components of the
43  * HostIdentifier are:
44  * <ul>
45  *   <li><p><tt>protocol</tt> - The communications protocol. If omitted,
46  *          and a hostname is not specified, then default local protocol,
47  *          <em>local:</em>, is assumed. If the protocol is omitted and a
48  *          hostname is specified then the default remote protocol,
49  *          <em>rmi:</em> is assumed.
50  *       </p></li>
51  *   <li><p><tt>hostname</tt> - The hostname. If omitted, then
52  *          <em>localhost</em> is assumed. If the protocol is also omitted,
53  *          then default local protocol <em>local:</em> is also assumed.
54  *          If the hostname is not omitted but the protocol is omitted,
55  *          then the default remote protocol, <em>rmi:</em> is assumed.
56  *       </p></li>
57  *   <li><p><tt>port</tt> - The port for the communications protocol.
58  *          Treatment of the <tt>port</tt> parameter is implementation
59  *          (protocol) specific. It is unused by the default local protocol,
60  *          <em>local:</em>. For the default remote protocol, <em>rmi:</em>,
61  *          <tt>port</tt> indicates the port number of the <em>rmiregistry</em>
62  *          on the target host and defaults to port 1099.
63  *       </p></li>
64  *   <li><p><tt>servername</tt> - The treatment of the Path, Query, and
65  *          Fragment components of the HostIdentifier are implementation
66  *          (protocol) dependent. These components are ignored by the
67  *          default local protocol, <em>local:</em>. For the default remote
68  *          protocol, <em>rmi</em>, the Path component is interpreted as
69  *          the name of the RMI remote object. The Query component may
70  *          contain an access mode specifier <em>?mode=</em> specifying
71  *          <em>"r"</em> or <em>"rw"</em> access (write access currently
72  *          ignored). The Fragment part is ignored.
73  *       </p></li>
74  * </ul>
75  * <p>
76  * All HostIdentifier objects are represented as absolute, hierarchical URIs.
77  * The constructors accept relative URIs, but these will generally be
78  * transformed into an absolute URI specifying a default protocol. A
79  * HostIdentifier differs from a URI in that certain contractions and
80  * illicit syntactical constructions are allowed. The following are all
81  * valid HostIdentifier strings:
82  *
83  * <ul>
84  *   <li><p>&lt null &gt - transformed into "//localhost"</p></li>
85  *   <li><p>localhost - transformed into "//localhost"</p></li>
86  *   <li><p>hostname - transformed into "//hostname"</p></li>
87  *   <li><p>hostname:port - transformed into "//hostname:port"</p></li>
88  *   <li><p>proto:hostname - transformed into "proto://hostname"</p></li>
89  *   <li><p>proto:hostname:port - transformed into
90  *          "proto://hostname:port"</p></li>
91  *   <li><p>proto://hostname:port</p></li>
92  * </ul>
93  * </p>
94  *
95  * @see URI
96  * @see VmIdentifier
97  *
98  * @author Brian Doherty
99  * @since 1.5
100  */
101 public class HostIdentifier {
102     private URI uri;
103 
104     /**
105      * creates a canonical representation of the uriString. This method
106      * performs certain translations depending on the type of URI generated
107      * by the string.
108      */
canonicalize(String uriString)109     private URI canonicalize(String uriString) throws URISyntaxException {
110         if ((uriString == null) || (uriString.compareTo("localhost") == 0)) {
111             uriString = "//localhost";
112             return new URI(uriString);
113         }
114 
115         URI u = new URI(uriString);
116 
117         if (u.isAbsolute()) {
118             if (u.isOpaque()) {
119                 /*
120                  * this code is here to deal with a special case. For ease of
121                  * use, we'd like to be able to handle the case where the user
122                  * specifies hostname:port, not requiring the scheme part.
123                  * This introduces some subtleties.
124                  *     hostname:port - scheme = hostname
125                  *                   - schemespecificpart = port
126                  *                   - hostname = null
127                  *                   - userinfo=null
128                  * however, someone could also enter scheme:hostname:port and
129                  * get into this code. the strategy is to consider this
130                  * syntax illegal and provide some code to defend against it.
131                  * Basically, we test that the string contains only one ":"
132                  * and that the ssp is numeric. If we get two colons, we will
133                  * attempt to insert the "//" after the first colon and then
134                  * try to create a URI from the resulting string.
135                  */
136                 String scheme = u.getScheme();
137                 String ssp = u.getSchemeSpecificPart();
138                 String frag = u.getFragment();
139                 URI u2 = null;
140 
141                 int c1index = uriString.indexOf(":");
142                 int c2index = uriString.lastIndexOf(":");
143                 if (c2index != c1index) {
144                     /*
145                      * this is the scheme:hostname:port case. Attempt to
146                      * transform this to scheme://hostname:port. If a path
147                      * part is part of the original strings, it will be
148                      * included in the SchemeSpecificPart. however, the
149                      * fragment part must be handled separately.
150                      */
151                     if (frag == null) {
152                         u2 = new URI(scheme + "://" + ssp);
153                     } else {
154                         u2 = new URI(scheme + "://" + ssp + "#" + frag);
155                     }
156                     return u2;
157                 }
158                 /*
159                  * here we have the <string>:<string> case, possibly with
160                  * optional path and fragment components. we assume that
161                  * the part following the colon is a number. we don't check
162                  * this condition here as it will get detected later anyway.
163                  */
164                 u2 = new URI("//" + uriString);
165                 return u2;
166             } else {
167                 return u;
168             }
169         } else {
170             /*
171              * This is the case where we were given a hostname followed
172              * by a path part, fragment part, or both a path and fragment
173              * part. The key here is that no scheme part was specified.
174              * For this case, if the scheme specific part does not begin
175              * with "//", then we prefix the "//" to the given string and
176              * attempt to create a URI from the resulting string.
177              */
178             String ssp = u.getSchemeSpecificPart();
179             if (ssp.startsWith("//")) {
180                 return u;
181             } else {
182                 return new URI("//" + uriString);
183             }
184         }
185     }
186 
187     /**
188      * Create a HostIdentifier instance from a string value.
189      *
190      * @param uriString a string representing a target host. The syntax of
191      *                  the string must conform to the rules specified in the
192      *                  class documentation.
193      *
194      * @throws URISyntaxException Thrown when the uriString or its canonical
195      *                            form is poorly formed. This exception may
196      *                            get encapsulated into a MonitorException in
197      *                            a future version.
198      *
199      */
HostIdentifier(String uriString)200     public HostIdentifier(String uriString) throws URISyntaxException {
201         uri = canonicalize(uriString);
202     }
203 
204     /**
205      * Create a HostIdentifier instance from component parts of a URI.
206      *
207      * @param scheme the {@link URI#getScheme} component of a URI.
208      * @param authority the {@link URI#getAuthority} component of a URI.
209      * @param path the {@link URI#getPath} component of a URI.
210      * @param query the {@link URI#getQuery} component of a URI.
211      * @param fragment the {@link URI#getFragment} component of a URI.
212      *
213      * @throws URISyntaxException Thrown when the uriString or its canonical
214      *                            form is poorly formed. This exception may
215      *                            get encapsulated into a MonitorException in
216      *                            a future version.
217      * @see URI
218      */
HostIdentifier(String scheme, String authority, String path, String query, String fragment)219     public HostIdentifier(String scheme, String authority, String path,
220                           String query, String fragment)
221            throws URISyntaxException {
222         uri = new URI(scheme, authority, path, query, fragment);
223     }
224 
225     /**
226      * Create a HostIdentifier instance from a VmIdentifier.
227      *
228      * The necessary components of the VmIdentifier are extracted and
229      * reassembled into a HostIdentifier. If a "file:" scheme (protocol)
230      * is specified, the the returned HostIdentifier will always be
231      * equivalent to HostIdentifier("file://localhost").
232      *
233      * @param vmid the VmIdentifier use to construct the HostIdentifier.
234      */
HostIdentifier(VmIdentifier vmid)235     public HostIdentifier(VmIdentifier vmid) {
236         /*
237          * Extract all components of the VmIdentifier URI except the
238          * user-info part of the authority (the lvmid).
239          */
240         StringBuilder sb = new StringBuilder();
241         String scheme = vmid.getScheme();
242         String host = vmid.getHost();
243         String authority = vmid.getAuthority();
244 
245         // check for 'file:' VmIdentifiers and handled as a special case.
246         if ((scheme != null) && (scheme.compareTo("file") == 0)) {
247             try {
248                 uri = new URI("file://localhost");
249             } catch (URISyntaxException e) { };
250             return;
251         }
252 
253         if ((host != null) && (host.compareTo(authority) == 0)) {
254             /*
255              * this condition occurs when the VmIdentifier specifies only
256              * the authority (i.e. the lvmid ), and not a host name.
257              */
258             host = null;
259         }
260 
261         if (scheme == null) {
262             if (host == null) {
263                 scheme = "local";            // default local scheme
264             } else {
265                 /*
266                  * rmi is the default remote scheme. if the VmIdentifier
267                  * specifies some other protocol, this default is overridden.
268                  */
269                 scheme = "rmi";
270             }
271         }
272 
273         sb.append(scheme).append("://");
274 
275         if (host == null) {
276             sb.append("localhost");          // default host name
277         } else {
278             sb.append(host);
279         }
280 
281         int port = vmid.getPort();
282         if (port != -1) {
283             sb.append(":").append(port);
284         }
285 
286         String path = vmid.getPath();
287         if ((path != null) && (path.length() != 0)) {
288             sb.append(path);
289         }
290 
291         String query = vmid.getQuery();
292         if (query != null) {
293             sb.append("?").append(query);
294         }
295 
296         String frag = vmid.getFragment();
297         if (frag != null) {
298             sb.append("#").append(frag);
299         }
300 
301         try {
302            uri = new URI(sb.toString());
303         } catch (URISyntaxException e) {
304            // shouldn't happen, as we were passed a valid VmIdentifier
305            throw new RuntimeException("Internal Error", e);
306         }
307     }
308 
309     /**
310      * Resolve a VmIdentifier with this HostIdentifier. A VmIdentifier, such
311      * as <em>1234</em> or <em>1234@hostname</em> or any other string that
312      * omits certain components of the URI string may be valid, but is certainly
313      * incomplete. They are missing critical information for identifying the
314      * the communications protocol, target host, or other parameters. A
315      * VmIdentifier of this form is considered <em>unresolved</em>. This method
316      * uses components of the HostIdentifier to resolve the missing components
317      * of the VmIdentifier.
318      * <p>
319      * Specified components of the unresolved VmIdentifier take precedence
320      * over their HostIdentifier counterparts. For example, if the VmIdentifier
321      * indicates <em>1234@hostname:2099</em> and the HostIdentifier indicates
322      * <em>rmi://hostname:1099/</em>, then the resolved VmIdentifier will
323      * be <em>rmi://1234@hostname:2099</em>. Any component not explicitly
324      * specified or assumed by the HostIdentifier, will remain unresolved in
325      * resolved VmIdentifier.
326      *  <p>
327      * A VmIdentifier specifying a <em>file:</em> scheme (protocol), is
328      * not changed in any way by this method.
329      *
330      * @param vmid the unresolved VmIdentifier.
331      * @return VmIdentifier - the resolved VmIdentifier. If vmid was resolved
332      *                        on entry to this method, then the returned
333      *                        VmIdentifier will be equal, but not identical, to
334      *                        vmid.
335      */
resolve(VmIdentifier vmid)336     public VmIdentifier resolve(VmIdentifier vmid)
337            throws URISyntaxException, MonitorException {
338         String scheme = vmid.getScheme();
339         String host = vmid.getHost();
340         String authority = vmid.getAuthority();
341 
342         if ((scheme != null) && (scheme.compareTo("file") == 0)) {
343             // don't attempt to resolve a file based VmIdentifier.
344             return vmid;
345         }
346 
347         if ((host != null) && (host.compareTo(authority) == 0)) {
348             /*
349              * this condition occurs when the VmIdentifier specifies only
350              * the authority (i.e. an lvmid), and not a host name.
351              */
352             host = null;
353         }
354 
355         if (scheme == null) {
356             scheme = getScheme();
357         }
358 
359         URI nuri = null;
360 
361         StringBuffer sb = new StringBuffer();
362 
363         sb.append(scheme).append("://");
364 
365         String userInfo = vmid.getUserInfo();
366         if (userInfo != null) {
367             sb.append(userInfo);
368         } else {
369             sb.append(vmid.getAuthority());
370         }
371 
372         if (host == null) {
373             host = getHost();
374         }
375         sb.append("@").append(host);
376 
377         int port = vmid.getPort();
378         if (port == -1) {
379             port = getPort();
380         }
381 
382         if (port != -1) {
383             sb.append(":").append(port);
384         }
385 
386         String path = vmid.getPath();
387         if ((path == null) || (path.length() == 0)) {
388             path = getPath();
389         }
390 
391         if ((path != null) && (path.length() > 0)) {
392             sb.append(path);
393         }
394 
395         String query = vmid.getQuery();
396         if (query == null) {
397             query = getQuery();
398         }
399         if (query != null) {
400             sb.append("?").append(query);
401         }
402 
403         String fragment = vmid.getFragment();
404         if (fragment == null) {
405             fragment = getFragment();
406         }
407         if (fragment != null) {
408             sb.append("#").append(fragment);
409         }
410 
411         String s = sb.toString();
412         return new VmIdentifier(s);
413     }
414 
415     /**
416      * Return the Scheme, or protocol, portion of this HostIdentifier.
417      *
418      * @return String - the scheme for this HostIdentifier.
419      * @see URI#getScheme()
420      */
getScheme()421     public String getScheme() {
422         return uri.isAbsolute() ? uri.getScheme() : null;
423     }
424 
425     /**
426      * Return the Scheme Specific Part of this HostIdentifier.
427      *
428      * @return String - the scheme specific part for this HostIdentifier.
429      * @see URI#getSchemeSpecificPart()
430      */
getSchemeSpecificPart()431     public String getSchemeSpecificPart() {
432         return  uri.getSchemeSpecificPart();
433     }
434 
435     /**
436      * Return the User Info part of this HostIdentifier.
437      *
438      * @return String - the user info part for this HostIdentifier.
439      * @see URI#getUserInfo()
440      */
getUserInfo()441     public String getUserInfo() {
442         return uri.getUserInfo();
443     }
444 
445     /**
446      * Return the Host part of this HostIdentifier.
447      *
448      * @return String - the host part for this HostIdentifier, or
449      *                  "localhost" if the URI.getHost() returns null.
450      * @see URI#getUserInfo()
451      */
getHost()452     public String getHost() {
453         return (uri.getHost() == null) ? "localhost" : uri.getHost();
454     }
455 
456     /**
457      * Return the Port for of this HostIdentifier.
458      *
459      * @return String - the port for this HostIdentifier
460      * @see URI#getPort()
461      */
getPort()462     public int getPort() {
463         return uri.getPort();
464     }
465 
466     /**
467      * Return the Path part of this HostIdentifier.
468      *
469      * @return String - the path part for this HostIdentifier.
470      * @see URI#getPath()
471      */
getPath()472     public String getPath() {
473         return uri.getPath();
474     }
475 
476     /**
477      * Return the Query part of this HostIdentifier.
478      *
479      * @return String - the query part for this HostIdentifier.
480      * @see URI#getQuery()
481      */
getQuery()482     public String getQuery() {
483         return uri.getQuery();
484     }
485 
486     /**
487      * Return the Fragment part of this HostIdentifier.
488      *
489      * @return String - the fragment part for this HostIdentifier.
490      * @see URI#getFragment()
491      */
getFragment()492     public String getFragment() {
493         return uri.getFragment();
494     }
495 
496     /**
497      * Return the mode indicated in this HostIdentifier.
498      *
499      * @return String - the mode string. If no mode is specified, then "r"
500      *                  is returned. otherwise, the specified mode is returned.
501      */
getMode()502     public String getMode() {
503         String query = getQuery();
504         if (query != null) {
505             String[] queryArgs = query.split("\\+");
506             for (int i = 0; i < queryArgs.length; i++) {
507                 if (queryArgs[i].startsWith("mode=")) {
508                     int index = queryArgs[i].indexOf('=');
509                     return queryArgs[i].substring(index+1);
510                 }
511             }
512         }
513         return "r";
514     }
515 
516     /**
517      * Return the URI associated with the HostIdentifier.
518      *
519      * @return URI - the URI.
520      * @see URI
521      */
getURI()522     public URI getURI() {
523         return uri;
524     }
525 
526     /**
527      * Return the hash code for this HostIdentifier. The hash code is
528      * identical to the hash code for the contained URI.
529      *
530      * @return int - the hashcode.
531      * @see URI#hashCode()
532      */
hashCode()533     public int hashCode() {
534         return uri.hashCode();
535     }
536 
537     /**
538      * Test for quality with other objects.
539      *
540      * @param object the object to be test for equality.
541      * @return boolean - returns true if the given object is of type
542      *                   HostIdentifier and its URI field is equal to this
543      *                   object's URI field. Otherwise, returns false.
544      *
545      * @see URI#equals(Object)
546      */
equals(Object object)547     public boolean equals(Object object) {
548         if (object == this) {
549             return true;
550         }
551         if (!(object instanceof HostIdentifier)) {
552             return false;
553         }
554         return uri.equals(((HostIdentifier)object).uri);
555     }
556 
557 
558     /**
559      * Convert to a string representation. Conversion is identical to
560      * calling getURI().toString(). This may change in a future release.
561      *
562      * @return String - a String representation of the HostIdentifier.
563      *
564      * @see URI#toString()
565      */
toString()566     public String toString() {
567         return uri.toString();
568     }
569 }
570