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      */
 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      */
 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      */
 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 returned HostIdentifier will always be
 231      * equivalent to HostIdentifier("file://localhost").
 232      *
 233      * @param vmid the VmIdentifier use to construct the HostIdentifier.
 234      */
 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      */
 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         StringBuilder sb = new StringBuilder();
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 566     public String toString() {
 567         return uri.toString();
 568     }
 569 }