1 /*
   2  * Copyright (c) 2008, 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 com.sun.servicetag;
  27 
  28 import java.io.*;
  29 import java.net.UnknownHostException;
  30 import java.util.Collection;
  31 import java.util.Collections;
  32 import java.util.HashSet;
  33 import java.util.LinkedHashMap;
  34 import java.util.Map;
  35 import java.util.Set;
  36 
  37 import static com.sun.servicetag.RegistrationDocument.*;
  38 
  39 /**
  40  * A {@code RegistrationData} object is a container of one or more
  41  * {@link #getServiceTags service tags} that identify the
  42  * components for product registration.
  43  * Each {@code RegistrationData} object has a {@link #getRegistrationURN
  44  * uniform resource name} (URN) as its identifier.
  45  * <a name="EnvMap"></a>
  46  * It also has an <i>environment map</i> with
  47  * the following elements:
  48  * <blockquote>
  49  *       <table border=0>
  50  *       <tr>
  51  *          <td><tt>hostname</tt></td>
  52  *          <td>Hostname of the system</td>
  53  *          <td>e.g. woody</td>
  54  *       </tr>
  55  *       <tr>
  56  *          <td><tt>hostId</tt></td>
  57  *          <td>Host ID of the system</td>
  58  *          <td>e.g. 83abc1ab</td>
  59  *       </tr>
  60  *       <tr>
  61  *          <td><tt>osName</tt></td>
  62  *          <td>Operating system name</td>
  63  *          <td> e.g. SunOS</td>
  64  *       </tr>
  65  *       <tr>
  66  *          <td><tt>osVersion</tt></td>
  67  *          <td>Operating system version</td>
  68  *          <td> e.g. 5.10</td>
  69  *       </tr>
  70  *       <tr>
  71  *          <td><tt>osArchitecture</tt></td>
  72  *          <td>Operating system architecture</td>
  73  *          <td> e.g. sparc</td>
  74  *       </tr>
  75  *       <tr>
  76  *          <td><tt>systemModel</tt></td>
  77  *          <td>System model</td>
  78  *          <td> e.g. SUNW,Sun-Fire-V440</td>
  79  *       </tr>
  80  *       <tr>
  81  *          <td><tt>systemManufacturer</tt></td>
  82  *          <td>System manufacturer</td>
  83  *          <td> e.g. Oracle Corporation</td>
  84  *       </tr>
  85  *       <tr>
  86  *          <td><tt>cpuManufacturer</tt></td>
  87  *          <td>CPU manufacturer</td>
  88  *          <td> e.g. Oracle Corporation</td>
  89  *       </tr>
  90  *       <tr>
  91  *          <td><tt>serialNumber</tt></td>
  92  *          <td>System serial number</td>
  93  *          <td> e.g. BEL078932</td>
  94  *       </tr>
  95  *       </table>
  96  *  </blockquote>
  97  * The <tt>hostname</tt> and <tt>osName</tt> element must have a non-empty value.
  98  * If an element is not available on a system and their value will be
  99  * empty.
 100  * <p>
 101  * <a name="XMLSchema">
 102  * <b>Registration XML Schema</b></a>
 103  * <p>
 104  * A {@code RegistrationData} object can be {@link #loadFromXML loaded} from
 105  * and {@link #storeToXML stored} into an XML file in the format described
 106  * by the
 107  * <a href="https://sn-tools.central.sun.com/twiki/pub/ServiceTags/RegistrationRelayService/product_registration.xsd">
 108  * registration data schema</a>.  The registration data schema is defined by the
 109  * Service Tags Technology.
 110  * <p>
 111  * Typically the registration data is constructed at installation time
 112  * and stored in an XML file for later service tag lookup or registration.
 113  *
 114  * <p>
 115  * <b>Example Usage</b>
 116  * <p>
 117  * The examples below show how the {@code RegistrationData} can be
 118  * used for product registration.
 119  * Exception handling is not shown in these examples for clarity.
 120  * <ol>
 121  * <li>This example shows how the JDK creates a JDK service tag, installs it
 122  * in the system service tag registry and adds it to the registration data.
 123  * <br>
 124  * <blockquote><pre>
 125  *   // create a service tag object with an instance_urn
 126  *   ServiceTag st = ServiceTag.newInstance(ServiceTag.generateInstanceURN(),
 127  *                                          ....);
 128  *   // Adds to the system service tag registry if supported
 129  *   if (Registry.isSupported()) {
 130  *       Registry.getSystemRegistry().addServiceTag(st);
 131  *   }
 132  *
 133  *   // add to the registration data
 134  *   RegistrationData registration  = new RegistrationData();
 135  *   registration.addServiceTag(st);
 136  * </pre></blockquote>
 137  * </li>
 138  * <li>At this point, the registration data is ready to
 139  * send to Sun Connection for registration. This example shows how to register
 140  * the JDK via the <i>Registration Relay Service</i>.
 141  * <p>
 142  * There are several registration services for Sun Connection. For example,
 143  * the <a href="https://sn-tools.central.sun.com/twiki/bin/view/ServiceTags/RegistrationRelayService">
 144  * Registration Relay Service</a> is a web application interface that
 145  * processes the registration data payload sent via HTTP post
 146  * and hosts the registration user interface for a specified
 147  * registration URL.  Refer to the
 148  * Registration Relay Service Specification for details.
 149  * <p>
 150  * <blockquote><pre>
 151  *   // Open the connection to the URL of the registration service
 152  *   HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
 153  *   con.setDoInput(true);
 154  *   con.setDoOutput(true);
 155  *   con.setUseCaches(false);
 156  *   con.setAllowUserInteraction(false);
 157  *   con.setRequestMethod("POST");
 158  *   con.setRequestProperty("Content-Type", "text/xml;charset=\"utf-8\"");
 159  *   con.connect();
 160  *
 161  *   // send the registration data to the registration service
 162  *   OutputStream out = con.getOutputStream();
 163  *   registration.storeToXML(out);
 164  *   out.close();
 165  * </pre></blockquote>
 166  * </li>
 167  * <li>This example shows how to store the registration data in an XML file.
 168  *     for later service tag lookup or registration.
 169  * <br>
 170  * <blockquote><pre>
 171  *   BufferedOutputStream out = new BufferedOutputStream(
 172  *       new FileOutputStream(""&lt;JAVA_HOME&gt;/lib/servicetag/registration.xml"));
 173  *   registration.storeToXML(out);
 174  *   out.close();
 175  * </pre></blockquote>
 176  * </li>
 177  * <li>This example shows how to install service tags that are in the
 178  * registration data in the system service tag registry when determined
 179  * to be available.  The system service tag registry might not have existed
 180  * when the registration data was constructed.
 181  * <br>
 182  * <blockquote><pre>
 183  *   if (Registry.isSupported()) {
 184  *       Set&lt;ServiceTag&gt; svctags = registration.getServiceTags();
 185  *       for (ServiceTag st : svctags) {
 186  *           Registry.getSystemRegistry().addServiceTag(st);
 187  *       }
 188  *   }
 189  * </pre></blockquote>
 190  * </li>
 191  * </ol>
 192  *
 193  * @see <a href="https://sunconnection.sun.com/inventory">Sun Connection Inventory Channel</a>
 194  */
 195 public class RegistrationData {
 196     private final Map<String, String> environment = initEnvironment();
 197     private final Map<String, ServiceTag> svcTagMap =
 198         new LinkedHashMap<String, ServiceTag>();
 199     private final String urn;
 200 
 201     /**
 202      * Creates a {@code RegistrationData} object with a generated
 203      * {@link #getRegistrationURN registration URN}.
 204      * The following keys in the {@link #getEnvironmentMap environment map}
 205      * will be initialized for the configuration of the
 206      * running system:
 207      * <blockquote>
 208      * <tt>hostname</tt>, <tt>osName</tt>,  <tt>osVersion</tt> and
 209      * <tt>osArchitecture</tt>
 210      * </blockquote>
 211      * and the value of other keys may be empty.
 212      */
 213     public RegistrationData() {
 214         this(Util.generateURN());
 215         SystemEnvironment sysEnv = SystemEnvironment.getSystemEnvironment();
 216         setEnvironment(ST_NODE_HOSTNAME, sysEnv.getHostname());
 217         setEnvironment(ST_NODE_HOST_ID, sysEnv.getHostId());
 218         setEnvironment(ST_NODE_OS_NAME, sysEnv.getOsName());
 219         setEnvironment(ST_NODE_OS_VERSION, sysEnv.getOsVersion());
 220         setEnvironment(ST_NODE_OS_ARCH, sysEnv.getOsArchitecture());
 221         setEnvironment(ST_NODE_SYSTEM_MODEL, sysEnv.getSystemModel());
 222         setEnvironment(ST_NODE_SYSTEM_MANUFACTURER, sysEnv.getSystemManufacturer());
 223         setEnvironment(ST_NODE_CPU_MANUFACTURER, sysEnv.getCpuManufacturer());
 224         setEnvironment(ST_NODE_SERIAL_NUMBER, sysEnv.getSerialNumber());
 225     }
 226 
 227     // package private
 228     RegistrationData(String urn) {
 229         this.urn = urn;
 230     }
 231 
 232     private Map<String, String> initEnvironment() {
 233         Map<String, String> map = new LinkedHashMap<String, String>();
 234         map.put(ST_NODE_HOSTNAME, "");
 235         map.put(ST_NODE_HOST_ID, "");
 236         map.put(ST_NODE_OS_NAME, "");
 237         map.put(ST_NODE_OS_VERSION, "");
 238         map.put(ST_NODE_OS_ARCH, "");
 239         map.put(ST_NODE_SYSTEM_MODEL, "");
 240         map.put(ST_NODE_SYSTEM_MANUFACTURER, "");
 241         map.put(ST_NODE_CPU_MANUFACTURER, "");
 242         map.put(ST_NODE_SERIAL_NUMBER, "");
 243         return map;
 244     }
 245 
 246     /**
 247      * Returns the uniform resource name of this registration data
 248      * in this format:
 249      * <tt>urn:st:&lt;32-char {@link java.util.UUID uuid}&gt;</tt>
 250      *
 251      * @return the URN of this registration data.
 252      */
 253     public String getRegistrationURN() {
 254         return urn;
 255     }
 256 
 257     /**
 258      * Returns a map containing the environment information for this
 259      * registration data.  See the set of <a href="#EnvMap">keys</a>
 260      * in the environment map. Subsequent update to the environment
 261      * map via the {@link #setEnvironment setEnvironment} method will not be reflected
 262      * in the returned map.
 263      *
 264      * @return an environment map for this registration data.
 265      */
 266     public Map<String, String> getEnvironmentMap() {
 267         return new LinkedHashMap<String,String>(environment);
 268     }
 269 
 270     /**
 271      * Sets an element of the specified {@code name} in the environment map
 272      * with the given {@code value}.
 273      *
 274      * @throws IllegalArgumentException if {@code name} is not a valid key
 275      * in the environment map, or {@code value} is not valid.
 276      */
 277     public void setEnvironment(String name, String value) {
 278         if (name == null) {
 279             throw new NullPointerException("name is null");
 280         }
 281         if (value == null) {
 282             throw new NullPointerException("value is null");
 283         }
 284         if (environment.containsKey(name)) {
 285             if (name.equals(ST_NODE_HOSTNAME) || name.equals(ST_NODE_OS_NAME)) {
 286                 if (value.length() == 0) {
 287                     throw new IllegalArgumentException("\"" +
 288                         name + "\" requires non-empty value.");
 289                 }
 290             }
 291             environment.put(name, value);
 292         } else {
 293             throw new IllegalArgumentException("\"" +
 294                  name + "\" is not an environment element.");
 295         }
 296     }
 297 
 298     /**
 299      * Returns all service tags in this registration data.
 300      *
 301      * @return  a {@link Set Set} of the service tags
 302      * in this registration data.
 303      */
 304     public Set<ServiceTag> getServiceTags() {
 305         return new HashSet<ServiceTag>(svcTagMap.values());
 306     }
 307 
 308     /**
 309      * Adds a service tag to this registration data.
 310      * If the given service tag has an empty <tt>instance_urn</tt>,
 311      * this method will generate a URN and place it in the copy
 312      * of the service tag in this registration data.
 313      * This method will return the {@code ServiceTag} object
 314      * added to this registration data.
 315      *
 316      * @param st {@code ServiceTag} object to be added.
 317      * @return a {@code ServiceTag} object added to this registration data.
 318      *
 319      * @throws IllegalArgumentException if
 320      *   a service tag of the same {@link ServiceTag#getInstanceURN
 321      *   <tt>instance_urn</tt>} already exists in the registry.
 322      */
 323     public synchronized ServiceTag addServiceTag(ServiceTag st) {
 324         ServiceTag svcTag = ServiceTag.newInstanceWithUrnTimestamp(st);
 325 
 326         String instanceURN = svcTag.getInstanceURN();
 327         if (svcTagMap.containsKey(instanceURN)) {
 328             throw new IllegalArgumentException("Instance_urn = " + instanceURN +
 329                     " already exists in the registration data.");
 330         } else {
 331             svcTagMap.put(instanceURN, svcTag);
 332         }
 333         return svcTag;
 334     }
 335 
 336     /**
 337      * Returns a service tag of the given <tt>instance_urn</tt> in this registration
 338      * data.
 339      *
 340      * @param instanceURN the <tt>instance_urn</tt> of the service tag
 341      * @return the {@code ServiceTag} object of the given  <tt>instance_urn</tt>
 342      *         if exists; otherwise return {@code null}.
 343      */
 344     public synchronized ServiceTag getServiceTag(String instanceURN) {
 345         if (instanceURN == null) {
 346             throw new NullPointerException("instanceURN is null");
 347         }
 348         return svcTagMap.get(instanceURN);
 349     }
 350 
 351     /**
 352      * Removes a service tag of the given <tt>instance_urn</tt> from this
 353      * registration data.
 354      *
 355      * @param instanceURN the <tt>instance_urn</tt> of
 356      * the service tag to be removed.
 357      *
 358      * @return the removed {@code ServiceTag} object;
 359      * or {@code null} if the service tag does not exist in this
 360      * registration data.
 361      */
 362     public synchronized ServiceTag removeServiceTag(String instanceURN) {
 363         if (instanceURN == null) {
 364             throw new NullPointerException("instanceURN is null");
 365         }
 366 
 367         ServiceTag svcTag = null;
 368         if (svcTagMap.containsKey(instanceURN)) {
 369             svcTag = svcTagMap.remove(instanceURN);
 370         }
 371         return svcTag;
 372     }
 373 
 374      /**
 375      * Updates the <tt>product_defined_instance_id</tt> in the service tag
 376      * of the given <tt>instance_urn</tt> in this registration data.
 377      *
 378      * @param instanceURN the <tt>instance_urn</tt> of the service tag to be updated.
 379      * @param productDefinedInstanceID the value of the
 380      * <tt>product_defined_instance_id</tt> to be set.
 381      *
 382      * @return the updated {@code ServiceTag} object;
 383      * or {@code null} if the service tag does not exist in this
 384      * registration data.
 385      */
 386     public synchronized ServiceTag updateServiceTag(String instanceURN,
 387                                                     String productDefinedInstanceID) {
 388         ServiceTag svcTag = getServiceTag(instanceURN);
 389         if (svcTag == null) {
 390             return null;
 391         }
 392 
 393         svcTag = ServiceTag.newInstanceWithUrnTimestamp(svcTag);
 394         // update the product defined instance ID field
 395         svcTag.setProductDefinedInstanceID(productDefinedInstanceID);
 396         svcTagMap.put(instanceURN, svcTag);
 397         return svcTag;
 398     }
 399 
 400     /**
 401      * Reads the registration data from the XML document on the
 402      * specified input stream.  The XML document must be
 403      * in the format described by the <a href="#XMLSchema">
 404      * registration data schema</a>.
 405      * The specified stream is closed after this method returns.
 406      *
 407      * @param in the input stream from which to read the XML document.
 408      * @return a {@code RegistrationData} object read from the input
 409      * stream.
 410      *
 411      * @throws IllegalArgumentException if the input stream
 412      * contains an invalid registration data.
 413      *
 414      * @throws IOException if an error occurred when reading from the input stream.
 415      */
 416     public static RegistrationData loadFromXML(InputStream in) throws IOException {
 417         try {
 418             return RegistrationDocument.load(in);
 419         } finally {
 420             in.close();
 421         }
 422     }
 423 
 424     /**
 425      * Writes the registration data to the specified output stream
 426      * in the format described by the <a href="#XMLSchema">
 427      * registration data schema</a> with "UTF-8" encoding.
 428      * The specified stream remains open after this method returns.
 429      *
 430      * @param os the output stream on which to write the XML document.
 431      *
 432      * @throws IOException if an error occurred when writing to the output stream.
 433      */
 434     public void storeToXML(OutputStream os) throws IOException {
 435         RegistrationDocument.store(os, this);
 436         os.flush();
 437     }
 438 
 439     /**
 440      * Returns a newly allocated byte array containing the registration
 441      * data in XML format.
 442      *
 443      * @return a newly allocated byte array containing the registration
 444      * data in XML format.
 445      */
 446     public byte[] toXML() {
 447         try {
 448             ByteArrayOutputStream out = new ByteArrayOutputStream();
 449             storeToXML(out);
 450             return out.toByteArray();
 451         } catch (IOException e) {
 452             // should not reach here
 453             return new byte[0];
 454         }
 455     }
 456 
 457     /**
 458      * Returns a string representation of this registration data in XML
 459      * format.
 460      *
 461      * @return a string representation of this registration data in XML
 462      *         format.
 463      */
 464     @Override
 465     public String toString() {
 466         try {
 467             ByteArrayOutputStream out = new ByteArrayOutputStream();
 468             storeToXML(out);
 469             return out.toString("UTF-8");
 470         } catch (IOException e) {
 471             // should not reach here
 472             return "Error creating the return string.";
 473         }
 474     }
 475 }