1 /*
   2  * Copyright (c) 2008, 2011, 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.util.HashSet;
  30 import java.util.Locale;
  31 import java.util.Properties;
  32 import java.util.Set;
  33 import java.util.List;
  34 import java.util.ArrayList;
  35 import static com.sun.servicetag.Util.*;
  36 
  37 /**
  38  * Service Tag Installer for Java SE.
  39  */
  40 public class Installer {
  41     // System properties for testing
  42     private static String SVCTAG_DIR_PATH =
  43         "servicetag.dir.path";
  44     private static String SVCTAG_ENABLE_REGISTRATION =
  45         "servicetag.registration.enabled";
  46     private final static String ORACLE = "Oracle";
  47     private final static String SUN = "Sun Microsystems";
  48     private final static String REGISTRATION_XML = "registration.xml";
  49     private final static String SERVICE_TAG_FILE = "servicetag";
  50     private final static String REGISTRATION_HTML_NAME = "register";
  51 
  52     private final static Locale[] knownSupportedLocales =
  53         new Locale[] { Locale.ENGLISH,
  54                        Locale.JAPANESE,
  55                        Locale.SIMPLIFIED_CHINESE};
  56 
  57     private final static String javaHome = System.getProperty("java.home");
  58     private static File svcTagDir;
  59     private static File serviceTagFile;
  60     private static File regXmlFile;
  61     private static RegistrationData registration;
  62     private static boolean supportRegistration;
  63     private static String registerHtmlParent;
  64     private static Set<Locale> supportedLocales = new HashSet<>();
  65     private static Properties svcTagProps = null;
  66     private static String[] jreArchs = null;
  67     static {
  68         String dir = System.getProperty(SVCTAG_DIR_PATH);
  69         if (dir == null) {
  70             svcTagDir = new File(getJrePath(), "lib" + File.separator + SERVICE_TAG_FILE);
  71         } else {
  72             svcTagDir = new File(dir);
  73         }
  74         serviceTagFile = new File(svcTagDir, SERVICE_TAG_FILE);
  75         regXmlFile = new File(svcTagDir, REGISTRATION_XML);
  76         if (System.getProperty(SVCTAG_ENABLE_REGISTRATION) == null) {
  77             supportRegistration = isJdk();
  78         } else {
  79             supportRegistration = true;
  80         }
  81     }
  82 
  83     private Installer() {
  84     }
  85 
  86     // Implementation of ServiceTag.getJavaServiceTag(String) method
  87     static ServiceTag getJavaServiceTag(String source) throws IOException {
  88         String vendor = System.getProperty("java.vendor", "");
  89         if (!vendor.startsWith(SUN) && !vendor.startsWith(ORACLE)) {
  90             // Products bundling this implementation may run on
  91             // Mac OS which is not a Sun/Oracle JDK
  92             return null;
  93         }
  94         boolean cleanup = false;
  95         try {
  96             // Check if we have the swordfish entries for this JRE version
  97             if (loadServiceTagProps() == null) {
  98                 return null;
  99             }
 100 
 101             ServiceTag st = getJavaServiceTag();
 102             // Check if the service tag created by this bundle owner
 103             if (st != null && st.getSource().equals(source)) {
 104                 // Install the system service tag if supported
 105                 // stclient may be installed after the service tag creation
 106                 if (Registry.isSupported()) {
 107                     installSystemServiceTag();
 108                 }
 109                 return st;
 110             }
 111 
 112             // in case any exception thrown during the cleanup
 113             cleanup = true;
 114 
 115             // re-create a new one for this bundle owner
 116             // first delete the registration data
 117             deleteRegistrationData();
 118             cleanup = false;
 119 
 120             // create service tag and generate new register.html pages
 121             return createServiceTag(source);
 122         } finally {
 123             if (cleanup) {
 124                 if (regXmlFile.exists()) {
 125                     regXmlFile.delete();
 126                 }
 127                 if (serviceTagFile.exists()) {
 128                     serviceTagFile.delete();
 129                 }
 130             }
 131         }
 132     }
 133 
 134     /**
 135      * Returns the Java SE registration data located in
 136      * the <JRE>/lib/servicetag/registration.xml by default.
 137      *
 138      * @throws IllegalArgumentException if the registration data
 139      *         is of invalid format.
 140      */
 141     private static synchronized RegistrationData getRegistrationData()
 142             throws IOException {
 143         if (registration != null) {
 144             return registration;
 145         }
 146         if (regXmlFile.exists()) {
 147             try (BufferedInputStream in =
 148                     new BufferedInputStream(new FileInputStream(regXmlFile)))
 149             {
 150                 registration = RegistrationData.loadFromXML(in);
 151             } catch (IllegalArgumentException ex) {
 152                 System.err.println("Error: Bad registration data \"" +
 153                                     regXmlFile + "\":" + ex.getMessage());
 154                 throw ex;
 155             }
 156         } else {
 157             registration = new RegistrationData();
 158         }
 159         return registration;
 160     }
 161 
 162     /**
 163      * Write the registration data to the registration.xml file.
 164      *
 165      * The offline registration page has to be regenerated with
 166      * the new registration data.
 167      *
 168      * @throws java.io.IOException
 169      */
 170     private static synchronized void writeRegistrationXml()
 171             throws IOException {
 172         if (!svcTagDir.exists()) {
 173             // This check is for NetBeans or other products that
 174             // bundles this com.sun.servicetag implementation for
 175             // pre-6u5 release.
 176             if (!svcTagDir.mkdir()) {
 177                 throw new IOException("Failed to create directory: " + svcTagDir);
 178             }
 179         }
 180 
 181         // regenerate the new offline registration page
 182         deleteRegistrationHtmlPage();
 183         getRegistrationHtmlPage();
 184 
 185         try (BufferedOutputStream out =
 186                 new BufferedOutputStream(new FileOutputStream(regXmlFile)))
 187         {
 188             getRegistrationData().storeToXML(out);
 189         } catch (IllegalArgumentException ex) {
 190             System.err.println("Error: Bad registration data \"" +
 191                                 regXmlFile + "\":" + ex.getMessage());
 192             throw ex;
 193         }
 194     }
 195 
 196     /**
 197      * Returns the instance urn(s) stored in the servicetag file
 198      * or empty set if file not exists.
 199      */
 200     private static Set<String> getInstalledURNs() throws IOException {
 201         Set<String> urnSet = new HashSet<>();
 202         if (serviceTagFile.exists()) {
 203             try (BufferedReader in = new BufferedReader(new FileReader(serviceTagFile))) {
 204                 String urn;
 205                 while ((urn = in.readLine()) != null) {
 206                     urn = urn.trim();
 207                     if (urn.length() > 0) {
 208                         urnSet.add(urn);
 209                     }
 210                 }
 211             }
 212         }
 213         return urnSet;
 214     }
 215 
 216     /**
 217      * Return the Java SE service tag(s) if it exists.
 218      * Typically only one Java SE service tag but it could have two for
 219      * Solaris 32-bit and 64-bit on the same install directory.
 220      *
 221      * @return the service tag(s) for Java SE
 222      */
 223     private static ServiceTag[] getJavaServiceTagArray() throws IOException {
 224         RegistrationData regData = getRegistrationData();
 225         Set<ServiceTag> svcTags = regData.getServiceTags();
 226         Set<ServiceTag> result = new HashSet<>();
 227 
 228         Properties props = loadServiceTagProps();
 229         String jdkUrn = props.getProperty("servicetag.jdk.urn");
 230         String jreUrn = props.getProperty("servicetag.jre.urn");
 231         for (ServiceTag st : svcTags) {
 232             if (st.getProductURN().equals(jdkUrn) ||
 233                 st.getProductURN().equals(jreUrn)) {
 234                 result.add(st);
 235             }
 236         }
 237         return result.toArray(new ServiceTag[0]);
 238     }
 239 
 240     /**
 241      * Returns the Java SE service tag for this running platform;
 242      * or null if not exist.
 243      * This method will return the 64-bit service tag if the JDK
 244      * supports both 32-bit and 64-bit if already created.
 245      */
 246     private static ServiceTag getJavaServiceTag() throws IOException {
 247         String definedId = getProductDefinedId();
 248         for (ServiceTag st : getJavaServiceTagArray()) {
 249             if (st.getProductDefinedInstanceID().equals(definedId)) {
 250                 return st;
 251             }
 252         }
 253         return null;
 254     }
 255 
 256     /**
 257      * Create a service tag for Java SE and install in the system
 258      * service tag registry if supported.
 259      *
 260      * A registration data <JRE>/lib/servicetag/registration.xml
 261      * will be created to storeToXML the XML entry for Java SE service tag.
 262      * If the system supports service tags, this method will install
 263      * the Java SE service tag in the system service tag registry and
 264      * its <tt>instance_urn</tt> will be stored to <JRE>/lib/servicetag/servicetag.
 265      *
 266      * If <JRE>/lib/servicetag/registration.xml exists but is not installed
 267      * in the system service tag registry (i.e. servicetag doesn't exist),
 268      * this method will install it as described above.
 269      *
 270      * If the system supports service tag, stclient will be used
 271      * to create the Java SE service tag.
 272      *
 273      * A Solaris 32-bit and 64-bit JDK will be installed in the same
 274      * directory but the registration.xml will have 2 service tags.
 275      * The servicetag file will also contain 2 instance_urns for that case.
 276      */
 277     private static ServiceTag createServiceTag(String svcTagSource)
 278             throws IOException {
 279         // determine if a new service tag is needed to be created
 280         ServiceTag newSvcTag = null;
 281         if (getJavaServiceTag() == null) {
 282             newSvcTag = newServiceTag(svcTagSource);
 283         }
 284 
 285         // Add the new service tag in the registration data
 286         if (newSvcTag != null) {
 287             RegistrationData regData = getRegistrationData();
 288 
 289             // Add the service tag to the registration data in JDK/JRE
 290             newSvcTag = regData.addServiceTag(newSvcTag);
 291 
 292             // add if there is a service tag for the OS
 293             ServiceTag osTag = SolarisServiceTag.getServiceTag();
 294             if (osTag != null && regData.getServiceTag(osTag.getInstanceURN()) == null) {
 295                 regData.addServiceTag(osTag);
 296             }
 297             // write to the registration.xml
 298             writeRegistrationXml();
 299         }
 300 
 301         // Install the system service tag if supported
 302         if (Registry.isSupported()) {
 303             installSystemServiceTag();
 304         }
 305         return newSvcTag;
 306     }
 307 
 308     private static void installSystemServiceTag() throws IOException {
 309         // only install the service tag in the registry if
 310         // it has permission to write the servicetag file.
 311         if ((!serviceTagFile.exists() && !svcTagDir.canWrite()) ||
 312                 (serviceTagFile.exists() && !serviceTagFile.canWrite())) {
 313             return;
 314         }
 315 
 316         Set<String> urns = getInstalledURNs();
 317         ServiceTag[] javaSvcTags = getJavaServiceTagArray();
 318         if (urns.size() < javaSvcTags.length) {
 319             for (ServiceTag st : javaSvcTags) {
 320                 // Add the service tag in the system service tag registry
 321                 // if not installed
 322                 String instanceURN = st.getInstanceURN();
 323                 if (!urns.contains(instanceURN)) {
 324                     Registry.getSystemRegistry().addServiceTag(st);
 325                 }
 326             }
 327         }
 328         writeInstalledUrns();
 329     }
 330 
 331     private static ServiceTag newServiceTag(String svcTagSource) throws IOException {
 332         Properties props = loadServiceTagProps();
 333 
 334         // Determine the product URN and name
 335         String productURN;
 336         String productName;
 337 
 338         if (isJdk()) {
 339             // <HOME>/jre exists which implies it's a JDK
 340             productURN = props.getProperty("servicetag.jdk.urn");
 341             productName = props.getProperty("servicetag.jdk.name");
 342         } else {
 343             // Otherwise, it's a JRE
 344             productURN = props.getProperty("servicetag.jre.urn");
 345             productName = props.getProperty("servicetag.jre.name");
 346         }
 347 
 348         return ServiceTag.newInstance(ServiceTag.generateInstanceURN(),
 349                                       productName,
 350                                       System.getProperty("java.version"),
 351                                       productURN,
 352                                       props.getProperty("servicetag.parent.name"),
 353                                       props.getProperty("servicetag.parent.urn"),
 354                                       getProductDefinedId(),
 355                                       System.getProperty("java.vendor"),
 356                                       System.getProperty("os.arch"),
 357                                       getZoneName(),
 358                                       svcTagSource);
 359     }
 360 
 361     /**
 362      * Delete the registration data, the offline registration pages and
 363      * the service tags in the system service tag registry if installed.
 364      *
 365      * The registration.xml and servicetag file will be removed.
 366      */
 367     private static synchronized void deleteRegistrationData()
 368             throws IOException {
 369         try {
 370             // delete the offline registration page
 371             deleteRegistrationHtmlPage();
 372 
 373             // Remove the service tag from the system ST registry if exists
 374             Set<String> urns = getInstalledURNs();
 375             if (urns.size() > 0 && Registry.isSupported()) {
 376                 for (String u : urns) {
 377                     Registry.getSystemRegistry().removeServiceTag(u);
 378                 }
 379             }
 380             registration = null;
 381         } finally {
 382             // Delete the registration.xml and servicetag files if exists
 383             if (regXmlFile.exists()) {
 384                 if (!regXmlFile.delete()) {
 385                     throw new IOException("Failed to delete " + regXmlFile);
 386                 }
 387             }
 388             if (serviceTagFile.exists()) {
 389                 if (!serviceTagFile.delete()) {
 390                     throw new IOException("Failed to delete " + serviceTagFile);
 391                 }
 392             }
 393         }
 394     }
 395 
 396     /**
 397      * Updates the registration data to contain one single service tag
 398      * for the running Java runtime.
 399      */
 400     private static synchronized void updateRegistrationData(String svcTagSource)
 401             throws IOException {
 402         RegistrationData regData = getRegistrationData();
 403         ServiceTag curSvcTag = newServiceTag(svcTagSource);
 404 
 405         ServiceTag[] javaSvcTags = getJavaServiceTagArray();
 406         Set<String> urns = getInstalledURNs();
 407         for (ServiceTag st : javaSvcTags) {
 408             if (!st.getProductDefinedInstanceID().equals(curSvcTag.getProductDefinedInstanceID())) {
 409                 String instanceURN = st.getInstanceURN();
 410                 regData.removeServiceTag(instanceURN);
 411 
 412                 // remove it from the system service tag registry if exists
 413                 if (urns.contains(instanceURN) && Registry.isSupported()) {
 414                     Registry.getSystemRegistry().removeServiceTag(instanceURN);
 415                 }
 416             }
 417         }
 418         writeRegistrationXml();
 419         writeInstalledUrns();
 420     }
 421 
 422     private static void writeInstalledUrns() throws IOException {
 423         // if the Registry is not supported,
 424         // remove the servicetag file
 425         if (!Registry.isSupported() && serviceTagFile.exists()) {
 426             serviceTagFile.delete();
 427             return;
 428         }
 429 
 430         try (PrintWriter out = new PrintWriter(serviceTagFile)) {
 431             ServiceTag[] javaSvcTags = getJavaServiceTagArray();
 432             for (ServiceTag st : javaSvcTags) {
 433                 // Write the instance_run to the servicetag file
 434                 String instanceURN = st.getInstanceURN();
 435                 out.println(instanceURN);
 436             }
 437         }
 438     }
 439 
 440     /**
 441      * Load the properties for generating Java SE service tags.
 442      *
 443      * @param version Version of Java SE
 444      */
 445     private static synchronized Properties loadServiceTagProps() throws IOException {
 446         if (svcTagProps != null) {
 447             return svcTagProps;
 448         }
 449 
 450         // For Java SE 8 and later releases, JDK and JRE both use
 451         // the same product number.  The sworRDFish metadata were
 452         // for legacy Sun part number.
 453         String filename = "/com/sun/servicetag/resources/javase_servicetag.properties";
 454         try (InputStream in = Installer.class.getResourceAsStream(filename)) {
 455             svcTagProps = new Properties();
 456             svcTagProps.load(in);
 457         }
 458         return svcTagProps;
 459     }
 460 
 461     /**
 462      * Returns the product defined instance ID for Java SE.
 463      * It is a list of comma-separated name/value pairs:
 464      *    "id=<full-version>  <arch> [<arch>]*"
 465      *    "dir=<java.home system property value>"
 466      *
 467      * where <full-version> is the full version string of the JRE,
 468      *       <arch> is the architecture that the runtime supports
 469      *       (i.e. "sparc", "sparcv9", "i386", "amd64" (ISA list))
 470      *
 471      * For Solaris, it can be dual mode that can support both
 472      * 32-bit and 64-bit. the "id" will be set to
 473      *     "1.6.0_03-b02 sparc sparcv9"
 474      *
 475      * The "dir" property is included in the service tag to enable
 476      * the Service Tag software to determine if a service tag for
 477      * Java SE is invalid and perform appropriate service tag
 478      * cleanup if necessary.  See RFE# 6574781 Service Tags Enhancement.
 479      *
 480      */
 481     private static String getProductDefinedId() {
 482         StringBuilder definedId = new StringBuilder();
 483         definedId.append("id=");
 484         definedId.append(System.getProperty("java.runtime.version"));
 485 
 486         String[] archs = getJreArchs();
 487         for (String name : archs) {
 488             definedId.append(" " + name);
 489         }
 490 
 491         String location = ",dir=" + javaHome;
 492         if ((definedId.length() + location.length()) < 256) {
 493             definedId.append(",dir=");
 494             definedId.append(javaHome);
 495         } else {
 496             // if it exceeds the limit, we will not include the location
 497             if (isVerbose()) {
 498                 System.err.println("Warning: Product defined instance ID exceeds the field limit:");
 499             }
 500         }
 501 
 502         return definedId.toString();
 503     }
 504 
 505     /**
 506      * Returns the architectures that the runtime supports
 507      *  (i.e. "sparc", "sparcv9", "i386", "amd64" (ISA list))
 508      * The directory name where libjava.so is located.
 509      *
 510      * On Windows, returns the "os.arch" system property value.
 511      */
 512     private synchronized static String[] getJreArchs() {
 513         if (jreArchs != null) {
 514             return jreArchs;
 515         }
 516 
 517         Set<String> archs = new HashSet<>();
 518 
 519         String os = System.getProperty("os.name");
 520         if (os.equals("SunOS") || os.equals("Linux")) {
 521             // Traverse the directories under <JRE>/lib.
 522             // If <JRE>/lib/<arch>/libjava.so exists, add <arch>
 523             // to the product defined ID
 524             File dir = new File(getJrePath() + File.separator + "lib");
 525             if (dir.isDirectory()) {
 526                 String[] children = dir.list();
 527                 for (String name : children) {
 528                     File f = new File(dir, name + File.separator + "libjava.so");
 529                     if (f.exists()) {
 530                         archs.add(name);
 531                     }
 532                 }
 533             }
 534         } else {
 535             // Windows - append the os.arch
 536             archs.add(System.getProperty("os.arch"));
 537         }
 538         jreArchs = archs.toArray(new String[0]);
 539         return jreArchs;
 540     }
 541 
 542     /**
 543      * Return the zonename if zone is supported; otherwise, return
 544      * "global".
 545      */
 546     private static String getZoneName() throws IOException {
 547         String zonename = "global";
 548 
 549         String command = "/usr/bin/zonename";
 550         File f = new File(command);
 551         // com.sun.servicetag package has to be compiled with JDK 5 as well
 552         // JDK 5 doesn't support the File.canExecute() method.
 553         // Risk not checking isExecute() for the zonename command is very low.
 554         if (f.exists()) {
 555             ProcessBuilder pb = new ProcessBuilder(command);
 556             Process p = pb.start();
 557             String output = commandOutput(p);
 558             if (p.exitValue() == 0) {
 559                 zonename = output.trim();
 560             }
 561 
 562         }
 563         return zonename;
 564     }
 565 
 566     private synchronized static String getRegisterHtmlParent() throws IOException {
 567         if (registerHtmlParent == null) {
 568             File htmlDir;    // register.html is put under the JDK directory
 569             if (getJrePath().endsWith(File.separator + "jre")) {
 570                 htmlDir = new File(getJrePath(), "..");
 571             } else {
 572                 // j2se non-image build
 573                 htmlDir = new File(getJrePath());
 574             }
 575 
 576             // initialize the supported locales
 577             initSupportedLocales(htmlDir);
 578 
 579             // Determine the location of the offline registration page
 580             String path = System.getProperty(SVCTAG_DIR_PATH);
 581             if (path == null) {
 582                 // Default is <JDK>/register.html
 583                 registerHtmlParent = htmlDir.getCanonicalPath();
 584             } else {
 585                 File f = new File(path);
 586                 registerHtmlParent = f.getCanonicalPath();
 587                 if (!f.isDirectory()) {
 588                     throw new InternalError("Path " + path + " set in \"" +
 589                             SVCTAG_DIR_PATH + "\" property is not a directory");
 590                 }
 591             }
 592         }
 593         return registerHtmlParent;
 594     }
 595 
 596     /**
 597      * Returns the File object of the offline registration page localized
 598      * for the default locale in the JDK directory.
 599      */
 600     static synchronized File getRegistrationHtmlPage() throws IOException {
 601         if (!supportRegistration) {
 602             // No register.html page generated if JRE
 603             return null;
 604         }
 605 
 606         String parent = getRegisterHtmlParent();
 607 
 608         // check if the offline registration page is already generated
 609         File f = new File(parent, REGISTRATION_HTML_NAME + ".html");
 610         if (!f.exists()) {
 611             // Generate the localized version of the offline registration Page
 612             generateRegisterHtml(parent);
 613         }
 614 
 615         String name = REGISTRATION_HTML_NAME;
 616         Locale locale = getDefaultLocale();
 617         if (!locale.equals(Locale.ENGLISH) && supportedLocales.contains(locale)) {
 618             // if the locale is not English and is supported by JDK
 619             // set to the appropriate offline registration page;
 620             // otherwise,set to register.html.
 621             name = REGISTRATION_HTML_NAME + "_" + locale.toString();
 622         }
 623         File htmlFile = new File(parent, name + ".html");
 624         if (isVerbose()) {
 625             System.out.print("Offline registration page: " + htmlFile);
 626             System.out.println((htmlFile.exists() ?
 627                                "" : " not exist. Use register.html"));
 628         }
 629         if (htmlFile.exists()) {
 630             return htmlFile;
 631         } else {
 632             return new File(parent,
 633                             REGISTRATION_HTML_NAME + ".html");
 634         }
 635     }
 636 
 637     private static Locale getDefaultLocale() {
 638         List<Locale> candidateLocales = getCandidateLocales(Locale.getDefault());
 639         for (Locale l : candidateLocales) {
 640             if (supportedLocales.contains(l)) {
 641                 return l;
 642             }
 643         }
 644         return Locale.getDefault();
 645     }
 646 
 647     private static List<Locale> getCandidateLocales(Locale locale) {
 648         String language = locale.getLanguage();
 649         String country = locale.getCountry();
 650         String variant = locale.getVariant();
 651 
 652         List<Locale> locales = new ArrayList<>(3);
 653         if (variant.length() > 0) {
 654             locales.add(locale);
 655         }
 656         if (country.length() > 0) {
 657             locales.add((locales.isEmpty()) ?
 658                         locale : new Locale(language, country, ""));
 659         }
 660         if (language.length() > 0) {
 661             locales.add((locales.isEmpty()) ?
 662                         locale : new Locale(language, "", ""));
 663         }
 664         return locales;
 665     }
 666 
 667     // Remove the offline registration pages
 668     private static void deleteRegistrationHtmlPage() throws IOException {
 669         String parent = getRegisterHtmlParent();
 670         if (parent == null) {
 671             return;
 672         }
 673 
 674         for (Locale locale : supportedLocales) {
 675             String name = REGISTRATION_HTML_NAME;
 676             if (!locale.equals(Locale.ENGLISH)) {
 677                 name += "_" + locale.toString();
 678             }
 679             File f = new File(parent, name + ".html");
 680             if (f.exists()) {
 681                 if (!f.delete()) {
 682                     throw new IOException("Failed to delete " + f);
 683                 }
 684             }
 685         }
 686     }
 687 
 688     private static void initSupportedLocales(File jdkDir) {
 689         if (supportedLocales.isEmpty()) {
 690             // initialize with the known supported locales
 691             for (Locale l : knownSupportedLocales) {
 692                 supportedLocales.add(l);
 693             }
 694         }
 695 
 696         // Determine unknown supported locales if any
 697         // by finding the localized version of README.html
 698         // This prepares if a new locale in JDK is supported in
 699         // e.g. in the OpenSource world
 700         FilenameFilter ff = new FilenameFilter() {
 701             public boolean accept(File dir, String name) {
 702                 String fname = name.toLowerCase();
 703                 if (fname.startsWith("readme") && fname.endsWith(".html")) {
 704                     return true;
 705                 }
 706                 return false;
 707             }
 708         };
 709 
 710         String[] readmes = jdkDir.list(ff);
 711         for (String name : readmes) {
 712             String basename = name.substring(0, name.length() - ".html".length());
 713             String[] ss = basename.split("_");
 714             switch (ss.length) {
 715                 case 1:
 716                     // English version
 717                     break;
 718                 case 2:
 719                     supportedLocales.add(new Locale(ss[1]));
 720                     break;
 721                 case 3:
 722                     supportedLocales.add(new Locale(ss[1], ss[2]));
 723                     break;
 724                 default:
 725                     // ignore
 726                     break;
 727             }
 728         }
 729         if (isVerbose()) {
 730             System.out.println("Supported locales: ");
 731             for (Locale l : supportedLocales) {
 732                 System.out.println(l);
 733             }
 734         }
 735     }
 736 
 737     private static final String JDK_HEADER_PNG_KEY = "@@JDK_HEADER_PNG@@";
 738     private static final String JDK_VERSION_KEY = "@@JDK_VERSION@@";
 739     private static final String REGISTRATION_URL_KEY = "@@REGISTRATION_URL@@";
 740     private static final String REGISTRATION_PAYLOAD_KEY = "@@REGISTRATION_PAYLOAD@@";
 741 
 742     @SuppressWarnings("unchecked")
 743     private static void generateRegisterHtml(String parent) throws IOException {
 744         int version = Util.getJdkVersion();
 745         int update = Util.getUpdateVersion();
 746         String jdkVersion = "Version " + version;
 747         if (update > 0) {
 748             // product name is not translated
 749             jdkVersion += " Update " + update;
 750         }
 751         RegistrationData regData = getRegistrationData();
 752         // Make sure it uses the canonical path before getting the URI.
 753         File img = new File(svcTagDir.getCanonicalPath(), "jdk_header.png");
 754         String headerImageSrc = img.toURI().toString();
 755 
 756         // Format the registration data in one single line
 757         StringBuilder payload = new StringBuilder();
 758         String xml = regData.toString().replaceAll("\"", "%22");
 759         try (BufferedReader reader = new BufferedReader(new StringReader(xml))) {
 760             String line = null;
 761             while ((line = reader.readLine()) != null) {
 762                 payload.append(line.trim());
 763             }
 764         }
 765 
 766         String resourceFilename = "/com/sun/servicetag/resources/register";
 767         for (Locale locale : supportedLocales) {
 768             String name = REGISTRATION_HTML_NAME;
 769             String resource = resourceFilename;
 770             if (!locale.equals(Locale.ENGLISH)) {
 771                 name += "_" + locale.toString();
 772                 resource += "_" + locale.toString();
 773             }
 774             File f = new File(parent, name + ".html");
 775             InputStream in = null;
 776             BufferedReader br = null;
 777             PrintWriter pw = null;
 778             String registerURL = SunConnection.
 779                 getRegistrationURL(regData.getRegistrationURN(),
 780                                    locale,
 781                                    String.valueOf(version)).toString();
 782             try {
 783                 in = Installer.class.getResourceAsStream(resource + ".html");
 784                 if (in == null) {
 785                     // if the resource file is missing
 786                     if (isVerbose()) {
 787                         System.out.println("Missing resouce file: " + resource + ".html");
 788                     }
 789                     continue;
 790                 }
 791                 if (isVerbose()) {
 792                     System.out.println("Generating " + f + " from " + resource + ".html");
 793                 }
 794 
 795                 try {
 796                     br = new BufferedReader(new InputStreamReader(in, "UTF-8"));
 797                     pw = new PrintWriter(f, "UTF-8");
 798                     String line = null;
 799                     while ((line = br.readLine()) != null) {
 800                         String output = line;
 801                         if (line.contains(JDK_VERSION_KEY)) {
 802                             output = line.replace(JDK_VERSION_KEY, jdkVersion);
 803                         } else if (line.contains(JDK_HEADER_PNG_KEY)) {
 804                             output = line.replace(JDK_HEADER_PNG_KEY, headerImageSrc);
 805                         } else if (line.contains(REGISTRATION_URL_KEY)) {
 806                             output = line.replace(REGISTRATION_URL_KEY, registerURL);
 807                         } else if (line.contains(REGISTRATION_PAYLOAD_KEY)) {
 808                             output = line.replace(REGISTRATION_PAYLOAD_KEY, payload.toString());
 809                         }
 810                         pw.println(output);
 811                     }
 812                     f.setReadOnly();
 813                     pw.flush();
 814                 } finally {
 815                     // It's safe for this finally block to have two close statements
 816                     // consecutively as PrintWriter.close doesn't throw IOException.
 817                     if (pw != null) {
 818                         pw.close();
 819                     }
 820                     if (br!= null) {
 821                         br.close();
 822                     }
 823                 }
 824             } finally {
 825                 if (in != null) {
 826                     in.close();
 827                 }
 828             }
 829         }
 830     }
 831 
 832     private static final int MAX_SOURCE_LEN = 63;
 833 
 834     /**
 835      * A utility class to create a service tag for Java SE.
 836      * <p>
 837      * <b>Usage:</b><br>
 838      * <blockquote><tt>
 839      * &lt;JAVA_HOME&gt;/bin/java com.sun.servicetag.Installer
 840      * </tt></blockquote>
 841      * <p>
 842      */
 843     public static void main(String[] args) {
 844         String source = "Manual ";
 845         String runtimeName = System.getProperty("java.runtime.name");
 846         if (runtimeName.startsWith("OpenJDK")) {
 847             source = "OpenJDK ";
 848         }
 849         source += System.getProperty("java.runtime.version");
 850         if (source.length() > MAX_SOURCE_LEN) {
 851             source = source.substring(0, MAX_SOURCE_LEN);
 852         }
 853 
 854         // Parse the options (arguments starting with "-" )
 855         boolean delete = false;
 856         boolean update = false;
 857         boolean register = false;
 858         int count = 0;
 859         while (count < args.length) {
 860             String arg = args[count];
 861             if (arg.trim().length() == 0) {
 862                 // skip empty arguments
 863                 count++;
 864                 continue;
 865             }
 866 
 867             if (arg.equals("-source")) {
 868                 source = args[++count];
 869             } else if (arg.equals("-delete")) {
 870                 delete = true;
 871             } else if (arg.equals("-register")) {
 872                 register = true;
 873             } else {
 874                 usage();
 875                 return;
 876             }
 877             count++;
 878         }
 879         try {
 880             if (delete) {
 881                 deleteRegistrationData();
 882             } else {
 883                 ServiceTag[] javaSvcTags = getJavaServiceTagArray();
 884                 String[] archs = getJreArchs();
 885                 if (javaSvcTags.length > archs.length) {
 886                     // 64-bit has been uninstalled
 887                     // so remove the service tag
 888                     updateRegistrationData(source);
 889                 } else {
 890                     // create the service tag
 891                     createServiceTag(source);
 892                 }
 893             }
 894 
 895             if (register) {
 896                 // Registration is only supported by JDK
 897                 // For testing purpose, override with a "servicetag.enable.registration" property
 898 
 899                 RegistrationData regData = getRegistrationData();
 900                 if (supportRegistration && !regData.getServiceTags().isEmpty()) {
 901                     SunConnection.register(regData,
 902                                            getDefaultLocale(),
 903                                            String.valueOf(Util.getJdkVersion()));
 904                 }
 905             }
 906             System.exit(0);
 907         } catch (IOException e) {
 908             System.err.println("I/O Error: " + e.getMessage());
 909             if (isVerbose()) {
 910                 e.printStackTrace();
 911             }
 912         } catch (IllegalArgumentException ex) {
 913             if (isVerbose()) {
 914                 ex.printStackTrace();
 915             }
 916         } catch (Exception e) {
 917             System.err.println("Error: " + e.getMessage());
 918             if (isVerbose()) {
 919                 e.printStackTrace();
 920             }
 921         }
 922         System.exit(1);
 923     }
 924 
 925     private static void usage() {
 926         System.out.println("Usage:");
 927         System.out.print("    " + Installer.class.getName());
 928         System.out.println(" [-delete|-source <source>|-register]");
 929         System.out.println("       to create a service tag for the Java platform");
 930         System.out.println("");
 931         System.out.println("Internal Options:");
 932         System.out.println("    -source: to specify the source of the service tag to be created");
 933         System.out.println("    -delete: to delete the service tag ");
 934         System.out.println("    -register: to register the JDK");
 935         System.out.println("    -help:   to print this help message");
 936     }
 937 }