/************************************************************************* * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright 2008 by Sun Microsystems, Inc. * * OpenOffice.org - a multi-platform office productivity suite * * $RCSfile: Installer.java,v $ * * $Revision: 1.2 $ * * This file is part of OpenOffice.org. * * OpenOffice.org is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 * only, as published by the Free Software Foundation. * * OpenOffice.org is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License version 3 for more details * (a copy is included in the LICENSE file that accompanied this code). * * You should have received a copy of the GNU Lesser General Public License * version 3 along with OpenOffice.org. If not, see * * for a copy of the LGPLv3 License. * ************************************************************************/ package com.sun.star.servicetag; import java.io.*; import java.util.HashSet; import java.util.Locale; import java.util.Properties; import java.util.Set; import java.util.List; import java.util.ArrayList; import static com.sun.star.servicetag.Util.*; /** * Service Tag Installer for Java SE. */ public class Installer { // System properties for testing private static String SVCTAG_DIR_PATH = "servicetag.dir.path"; private static String SVCTAG_ENABLE_REGISTRATION = "servicetag.registration.enabled"; private final static String SUN_VENDOR = "Sun Microsystems"; private final static String REGISTRATION_XML = "registration.xml"; private final static String SERVICE_TAG_FILE = "servicetag"; private final static String REGISTRATION_HTML_NAME = "register"; private final static Locale[] knownSupportedLocales = new Locale[] { Locale.ENGLISH, Locale.JAPANESE, Locale.SIMPLIFIED_CHINESE}; private final static String javaHome = System.getProperty("java.home"); private static File svcTagDir; private static File serviceTagFile; private static File regXmlFile; private static RegistrationData registration; private static boolean supportRegistration; private static String registerHtmlParent; private static Set supportedLocales = new HashSet(); private static Properties swordfishProps = null; private static String[] jreArchs = null; static { String dir = System.getProperty(SVCTAG_DIR_PATH); if (dir == null) { svcTagDir = new File(getJrePath(), "lib" + File.separator + SERVICE_TAG_FILE); } else { svcTagDir = new File(dir); } serviceTagFile = new File(svcTagDir, SERVICE_TAG_FILE); regXmlFile = new File(svcTagDir, REGISTRATION_XML); if (System.getProperty(SVCTAG_ENABLE_REGISTRATION) == null) { supportRegistration = isJdk(); } else { supportRegistration = true; } } private Installer() { } // Implementation of ServiceTag.getJavaServiceTag(String) method static ServiceTag getJavaServiceTag(String source) throws IOException { if (!System.getProperty("java.vendor").startsWith(SUN_VENDOR)) { // Products bundling this implementation may run on // Mac OS which is not a Sun JDK return null; } boolean cleanup = false; try { // Check if we have the swordfish entries for this JRE version if (loadSwordfishEntries() == null) { return null; } ServiceTag st = getJavaServiceTag(); // Check if the service tag created by this bundle owner if (st != null && st.getSource().equals(source)) { // Install the system service tag if supported // stclient may be installed after the service tag creation if (Registry.isSupported()) { installSystemServiceTag(); } return st; } // in case any exception thrown during the cleanup cleanup = true; // re-create a new one for this bundle owner // first delete the registration data deleteRegistrationData(); cleanup = false; // create service tag and generate new register.html pages return createServiceTag(source); } finally { if (cleanup) { if (regXmlFile.exists()) { regXmlFile.delete(); } if (serviceTagFile.exists()) { serviceTagFile.delete(); } } } } /** * Returns the Java SE registration data located in * the /lib/servicetag/registration.xml by default. * * @throws IllegalArgumentException if the registration data * is of invalid format. */ private static synchronized RegistrationData getRegistrationData() throws IOException { if (registration != null) { return registration; } if (regXmlFile.exists()) { BufferedInputStream in = null; try { in = new BufferedInputStream(new FileInputStream(regXmlFile)); registration = RegistrationData.loadFromXML(in); } catch (IllegalArgumentException ex) { System.err.println("Error: Bad registration data \"" + regXmlFile + "\":" + ex.getMessage()); throw ex; } finally { if (in != null) { in.close(); } } } else { registration = new RegistrationData(); } return registration; } /** * Write the registration data to the registration.xml file. * * The offline registration page has to be regenerated with * the new registration data. * * @throws java.io.IOException */ private static synchronized void writeRegistrationXml() throws IOException { if (!svcTagDir.exists()) { // This check is for NetBeans or other products that // bundles this com.sun.servicetag implementation for // pre-6u5 release. if (!svcTagDir.mkdir()) { throw new IOException("Failed to create directory: " + svcTagDir); } } // regenerate the new offline registration page deleteRegistrationHtmlPage(); getRegistrationHtmlPage(); BufferedOutputStream out = null; try { out = new BufferedOutputStream(new FileOutputStream(regXmlFile)); getRegistrationData().storeToXML(out); } catch (IllegalArgumentException ex) { System.err.println("Error: Bad registration data \"" + regXmlFile + "\":" + ex.getMessage()); throw ex; } finally { if (out != null) { out.close(); } } } /** * Returns the instance urn(s) stored in the servicetag file * or empty set if file not exists. */ private static Set getInstalledURNs() throws IOException { Set urnSet = new HashSet(); if (serviceTagFile.exists()) { BufferedReader in = null; try { in = new BufferedReader(new FileReader(serviceTagFile)); String urn; while ((urn = in.readLine()) != null) { urn = urn.trim(); if (urn.length() > 0) { urnSet.add(urn); } } } finally { if (in != null) { in.close(); } } } return urnSet; } /** * Return the Java SE service tag(s) if it exists. * Typically only one Java SE service tag but it could have two for * Solaris 32-bit and 64-bit on the same install directory. * * @return the service tag(s) for Java SE */ private static ServiceTag[] getJavaServiceTagArray() throws IOException { RegistrationData regData = getRegistrationData(); Set svcTags = regData.getServiceTags(); Set result = new HashSet(); Properties props = loadSwordfishEntries(); String jdkUrn = props.getProperty("servicetag.jdk.urn"); String jreUrn = props.getProperty("servicetag.jre.urn"); for (ServiceTag st : svcTags) { if (st.getProductURN().equals(jdkUrn) || st.getProductURN().equals(jreUrn)) { result.add(st); } } return result.toArray(new ServiceTag[0]); } /** * Returns the Java SE service tag for this running platform; * or null if not exist. * This method will return the 64-bit service tag if the JDK * supports both 32-bit and 64-bit if already created. */ private static ServiceTag getJavaServiceTag() throws IOException { String definedId = getProductDefinedId(); for (ServiceTag st : getJavaServiceTagArray()) { if (st.getProductDefinedInstanceID().equals(definedId)) { return st; } } return null; } /** * Create a service tag for Java SE and install in the system * service tag registry if supported. * * A registration data /lib/servicetag/registration.xml * will be created to storeToXML the XML entry for Java SE service tag. * If the system supports service tags, this method will install * the Java SE service tag in the system service tag registry and * its instance_urn will be stored to /lib/servicetag/servicetag. * * If /lib/servicetag/registration.xml exists but is not installed * in the system service tag registry (i.e. servicetag doesn't exist), * this method will install it as described above. * * If the system supports service tag, stclient will be used * to create the Java SE service tag. * * A Solaris 32-bit and 64-bit JDK will be installed in the same * directory but the registration.xml will have 2 service tags. * The servicetag file will also contain 2 instance_urns for that case. */ private static ServiceTag createServiceTag(String svcTagSource) throws IOException { // determine if a new service tag is needed to be created ServiceTag newSvcTag = null; if (getJavaServiceTag() == null) { newSvcTag = newServiceTag(svcTagSource); } // Add the new service tag in the registration data if (newSvcTag != null) { RegistrationData regData = getRegistrationData(); // Add the service tag to the registration data in JDK/JRE newSvcTag = regData.addServiceTag(newSvcTag); // add if there is a service tag for the OS ServiceTag osTag = SolarisServiceTag.getServiceTag(); if (osTag != null && regData.getServiceTag(osTag.getInstanceURN()) == null) { regData.addServiceTag(osTag); } // write to the registration.xml writeRegistrationXml(); } // Install the system service tag if supported if (Registry.isSupported()) { installSystemServiceTag(); } return newSvcTag; } private static void installSystemServiceTag() throws IOException { // only install the service tag in the registry if // it has permission to write the servicetag file. if ((!serviceTagFile.exists() && !svcTagDir.canWrite()) || (serviceTagFile.exists() && !serviceTagFile.canWrite())) { return; } Set urns = getInstalledURNs(); ServiceTag[] javaSvcTags = getJavaServiceTagArray(); if (urns.size() < javaSvcTags.length) { for (ServiceTag st : javaSvcTags) { // Add the service tag in the system service tag registry // if not installed String instanceURN = st.getInstanceURN(); if (!urns.contains(instanceURN)) { Registry.getSystemRegistry().addServiceTag(st); } } } writeInstalledUrns(); } private static ServiceTag newServiceTag(String svcTagSource) throws IOException { // Load the swoRDFish information for the service tag creation Properties props = loadSwordfishEntries(); // Determine the product URN and name String productURN; String productName; if (isJdk()) { // /jre exists which implies it's a JDK productURN = props.getProperty("servicetag.jdk.urn"); productName = props.getProperty("servicetag.jdk.name"); } else { // Otherwise, it's a JRE productURN = props.getProperty("servicetag.jre.urn"); productName = props.getProperty("servicetag.jre.name"); } return ServiceTag.newInstance(ServiceTag.generateInstanceURN(), productName, System.getProperty("java.version"), productURN, props.getProperty("servicetag.parent.name"), props.getProperty("servicetag.parent.urn"), getProductDefinedId(), SUN_VENDOR, System.getProperty("os.arch"), getZoneName(), svcTagSource); } /** * Delete the registration data, the offline registration pages and * the service tags in the system service tag registry if installed. * * The registration.xml and servicetag file will be removed. */ private static synchronized void deleteRegistrationData() throws IOException { try { // delete the offline registration page deleteRegistrationHtmlPage(); // Remove the service tag from the system ST registry if exists Set urns = getInstalledURNs(); if (urns.size() > 0 && Registry.isSupported()) { for (String u : urns) { Registry.getSystemRegistry().removeServiceTag(u); } } registration = null; } finally { // Delete the registration.xml and servicetag files if exists if (regXmlFile.exists()) { if (!regXmlFile.delete()) { throw new IOException("Failed to delete " + regXmlFile); } } if (serviceTagFile.exists()) { if (!serviceTagFile.delete()) { throw new IOException("Failed to delete " + serviceTagFile); } } } } /** * Updates the registration data to contain one single service tag * for the running Java runtime. */ private static synchronized void updateRegistrationData(String svcTagSource) throws IOException { RegistrationData regData = getRegistrationData(); ServiceTag curSvcTag = newServiceTag(svcTagSource); ServiceTag[] javaSvcTags = getJavaServiceTagArray(); Set urns = getInstalledURNs(); for (ServiceTag st : javaSvcTags) { if (!st.getProductDefinedInstanceID().equals(curSvcTag.getProductDefinedInstanceID())) { String instanceURN = st.getInstanceURN(); regData.removeServiceTag(instanceURN); // remove it from the system service tag registry if exists if (urns.contains(instanceURN) && Registry.isSupported()) { Registry.getSystemRegistry().removeServiceTag(instanceURN); } } } writeRegistrationXml(); writeInstalledUrns(); } private static void writeInstalledUrns() throws IOException { // if the Registry is not supported, // remove the servicetag file if (!Registry.isSupported() && serviceTagFile.exists()) { serviceTagFile.delete(); return; } PrintWriter out = null; try { out = new PrintWriter(serviceTagFile); ServiceTag[] javaSvcTags = getJavaServiceTagArray(); for (ServiceTag st : javaSvcTags) { // Write the instance_run to the servicetag file String instanceURN = st.getInstanceURN(); out.println(instanceURN); } } finally { if (out != null) { out.close(); } } } /** * Load the values associated with the swoRDFish metadata entries * for Java SE. The swoRDFish metadata entries are different for * different release. * * @param version Version of Java SE */ private static synchronized Properties loadSwordfishEntries() throws IOException { if (swordfishProps != null) { return swordfishProps; } // The version string for Java SE 6 is 1.6.0 // We just need the minor number in the version string int version = Util.getJdkVersion(); String filename = "/com/sun/servicetag/resources/javase_" + version + "_swordfish.properties"; InputStream in = Installer.class.getClass().getResourceAsStream(filename); if (in == null) { return null; } swordfishProps = new Properties(); try { swordfishProps.load(in); } finally { in.close(); } return swordfishProps; } /** * Returns the product defined instance ID for Java SE. * It is a list of comma-separated name/value pairs: * "id= []*" * "dir=" * * where is the full version string of the JRE, * is the architecture that the runtime supports * (i.e. "sparc", "sparcv9", "i386", "amd64" (ISA list)) * * For Solaris, it can be dual mode that can support both * 32-bit and 64-bit. the "id" will be set to * "1.6.0_03-b02 sparc sparcv9" * * The "dir" property is included in the service tag to enable * the Service Tag software to determine if a service tag for * Java SE is invalid and perform appropriate service tag * cleanup if necessary. See RFE# 6574781 Service Tags Enhancement. * */ private static String getProductDefinedId() { StringBuilder definedId = new StringBuilder(); definedId.append("id="); definedId.append(System.getProperty("java.runtime.version")); String[] archs = getJreArchs(); for (String name : archs) { definedId.append(" " + name); } String location = ",dir=" + javaHome; if ((definedId.length() + location.length()) < 256) { definedId.append(",dir="); definedId.append(javaHome); } else { // if it exceeds the limit, we will not include the location if (isVerbose()) { System.err.println("Warning: Product defined instance ID exceeds the field limit:"); } } return definedId.toString(); } /** * Returns the architectures that the runtime supports * (i.e. "sparc", "sparcv9", "i386", "amd64" (ISA list)) * The directory name where libjava.so is located. * * On Windows, returns the "os.arch" system property value. */ private synchronized static String[] getJreArchs() { if (jreArchs != null) { return jreArchs; } Set archs = new HashSet(); String os = System.getProperty("os.name"); if (os.equals("SunOS") || os.equals("Linux")) { // Traverse the directories under /lib. // If /lib//libjava.so exists, add // to the product defined ID File dir = new File(getJrePath() + File.separator + "lib"); if (dir.isDirectory()) { String[] children = dir.list(); for (String name : children) { File f = new File(dir, name + File.separator + "libjava.so"); if (f.exists()) { archs.add(name); } } } } else { // Windows - append the os.arch archs.add(System.getProperty("os.arch")); } jreArchs = archs.toArray(new String[0]); return jreArchs; } /** * Return the zonename if zone is supported; otherwise, return * "global". */ public static String getZoneName() throws IOException { String zonename = "global"; String command = "/usr/bin/zonename"; File f = new File(command); // com.sun.servicetag package has to be compiled with JDK 5 as well // JDK 5 doesn't support the File.canExecute() method. // Risk not checking isExecute() for the zonename command is very low. if (f.exists()) { ProcessBuilder pb = new ProcessBuilder(command); Process p = pb.start(); String output = commandOutput(p); if (p.exitValue() == 0) { zonename = output.trim(); } } return zonename; } private synchronized static String getRegisterHtmlParent() throws IOException { if (registerHtmlParent == null) { File htmlDir; // register.html is put under the JDK directory if (getJrePath().endsWith(File.separator + "jre")) { htmlDir = new File(getJrePath(), ".."); } else { // j2se non-image build htmlDir = new File(getJrePath()); } // initialize the supported locales initSupportedLocales(htmlDir); // Determine the location of the offline registration page String path = System.getProperty(SVCTAG_DIR_PATH); if (path == null) { // Default is /register.html registerHtmlParent = htmlDir.getCanonicalPath(); } else { File f = new File(path); registerHtmlParent = f.getCanonicalPath(); if (!f.isDirectory()) { throw new InternalError("Path " + path + " set in \"" + SVCTAG_DIR_PATH + "\" property is not a directory"); } } } return registerHtmlParent; } /** * Returns the File object of the offline registration page localized * for the default locale in the JDK directory. */ static synchronized File getRegistrationHtmlPage() throws IOException { if (!supportRegistration) { // No register.html page generated if JRE return null; } String parent = getRegisterHtmlParent(); // check if the offline registration page is already generated File f = new File(parent, REGISTRATION_HTML_NAME + ".html"); if (!f.exists()) { // Generate the localized version of the offline registration Page generateRegisterHtml(parent); } String name = REGISTRATION_HTML_NAME; List candidateLocales = getCandidateLocales(Locale.getDefault()); for (Locale l : candidateLocales) { if (supportedLocales.contains(l)) { name = REGISTRATION_HTML_NAME + "_" + l.toString(); break; } } File htmlFile = new File(parent, name + ".html"); if (isVerbose()) { System.out.print("Offline registration page: " + htmlFile); System.out.println((htmlFile.exists() ? "" : " not exist. Use register.html")); } if (htmlFile.exists()) { return htmlFile; } else { return new File(parent, REGISTRATION_HTML_NAME + ".html"); } } private static List getCandidateLocales(Locale locale) { String language = locale.getLanguage(); String country = locale.getCountry(); String variant = locale.getVariant(); List locales = new ArrayList(3); if (variant.length() > 0) { locales.add(locale); } if (country.length() > 0) { locales.add((locales.size() == 0) ? locale : new Locale(language, country, "")); } if (language.length() > 0) { locales.add((locales.size() == 0) ? locale : new Locale(language, "", "")); } return locales; } // Remove the offline registration pages private static void deleteRegistrationHtmlPage() throws IOException { String parent = getRegisterHtmlParent(); if (parent == null) { return; } for (Locale locale : supportedLocales) { String name = REGISTRATION_HTML_NAME; if (!locale.equals(Locale.ENGLISH)) { name += "_" + locale.toString(); } File f = new File(parent, name + ".html"); if (f.exists()) { if (!f.delete()) { throw new IOException("Failed to delete " + f); } } } } private static void initSupportedLocales(File jdkDir) { if (supportedLocales.isEmpty()) { // initialize with the known supported locales for (Locale l : knownSupportedLocales) { supportedLocales.add(l); } } // Determine unknown supported locales if any // by finding the localized version of README.html // This prepares if a new locale in JDK is supported in // e.g. in the OpenSource world FilenameFilter ff = new FilenameFilter() { public boolean accept(File dir, String name) { String fname = name.toLowerCase(); if (fname.startsWith("readme") && fname.endsWith(".html")) { return true; } return false; } }; String[] readmes = jdkDir.list(ff); for (String name : readmes) { String basename = name.substring(0, name.length() - ".html".length()); String[] ss = basename.split("_"); switch (ss.length) { case 1: // English version break; case 2: supportedLocales.add(new Locale(ss[1])); break; case 3: supportedLocales.add(new Locale(ss[1], ss[2])); break; default: // ignore break; } } if (isVerbose()) { System.out.println("Supported locales: "); for (Locale l : supportedLocales) { System.out.println(l); } } } private static final String JDK_HEADER_PNG_KEY = "@@JDK_HEADER_PNG@@"; private static final String JDK_VERSION_KEY = "@@JDK_VERSION@@"; private static final String REGISTRATION_URL_KEY = "@@REGISTRATION_URL@@"; private static final String REGISTRATION_PAYLOAD_KEY = "@@REGISTRATION_PAYLOAD@@"; @SuppressWarnings("unchecked") private static void generateRegisterHtml(String parent) throws IOException { int version = Util.getJdkVersion(); int update = Util.getUpdateVersion(); String jdkVersion = "Version " + version; if (update > 0) { // product name is not translated jdkVersion += " Update " + update; } RegistrationData regData = getRegistrationData(); String registerURL = SunConnection.getRegistrationURL( regData.getRegistrationURN()).toString(); // Make sure it uses the canonical path before getting the URI. File img = new File(svcTagDir.getCanonicalPath(), "jdk_header.png"); String headerImageSrc = img.toURI().toString(); // Format the registration data in one single line StringBuilder payload = new StringBuilder(); String xml = regData.toString().replaceAll("\"", "%22"); BufferedReader reader = new BufferedReader(new StringReader(xml)); try { String line = null; while ((line = reader.readLine()) != null) { payload.append(line.trim()); } } finally { reader.close(); } String resourceFilename = "/com/sun/star/servicetag/resources/register"; for (Locale locale : supportedLocales) { String name = REGISTRATION_HTML_NAME; String resource = resourceFilename; if (!locale.equals(Locale.ENGLISH)) { name += "_" + locale.toString(); resource += "_" + locale.toString(); } File f = new File(parent, name + ".html"); InputStream in = null; BufferedReader br = null; PrintWriter pw = null; try { in = Installer.class.getClass().getResourceAsStream(resource + ".html"); if (in == null) { // if the resource file is missing if (isVerbose()) { System.out.println("Missing resouce file: " + resource + ".html"); } continue; } if (isVerbose()) { System.out.println("Generating " + f + " from " + resource + ".html"); } br = new BufferedReader(new InputStreamReader(in, "UTF-8")); pw = new PrintWriter(f, "UTF-8"); String line = null; while ((line = br.readLine()) != null) { String output = line; if (line.contains(JDK_VERSION_KEY)) { output = line.replace(JDK_VERSION_KEY, jdkVersion); } else if (line.contains(JDK_HEADER_PNG_KEY)) { output = line.replace(JDK_HEADER_PNG_KEY, headerImageSrc); } else if (line.contains(REGISTRATION_URL_KEY)) { output = line.replace(REGISTRATION_URL_KEY, registerURL); } else if (line.contains(REGISTRATION_PAYLOAD_KEY)) { output = line.replace(REGISTRATION_PAYLOAD_KEY, payload.toString()); } pw.println(output); } f.setReadOnly(); pw.flush(); } finally { if (pw != null) { pw.close(); } if (in != null) { in.close(); } if (br!= null) { br.close(); } } } } /** * A utility class to create a service tag for Java SE. *

* Usage:
*

* <JAVA_HOME>/bin/java com.sun.servicetag.Installer *
*

*/ public static void main(String[] args) { String source = "Manual"; // Parse the options (arguments starting with "-" ) boolean delete = false; boolean update = false; boolean register = false; int count = 0; while (count < args.length) { String arg = args[count]; if (arg.trim().length() == 0) { // skip empty arguments count++; continue; } if (arg.equals("-source")) { source = args[++count]; } else if (arg.equals("-delete")) { delete = true; } else if (arg.equals("-register")) { register = true; } else { usage(); return; } count++; } try { if (delete) { deleteRegistrationData(); } else { ServiceTag[] javaSvcTags = getJavaServiceTagArray(); String[] archs = getJreArchs(); if (javaSvcTags.length > archs.length) { // 64-bit has been uninstalled // so remove the service tag updateRegistrationData(source); } else { // create the service tag createServiceTag(source); } } if (register) { // Registration is only supported by JDK // For testing purpose, override with a "servicetag.enable.registration" property RegistrationData regData = getRegistrationData(); if (supportRegistration && !regData.getServiceTags().isEmpty()) { SunConnection.register(regData); } } System.exit(0); } catch (IOException e) { System.err.println("I/O Error: " + e.getMessage()); if (isVerbose()) { e.printStackTrace(); } } catch (IllegalArgumentException ex) { if (isVerbose()) { ex.printStackTrace(); } } catch (Exception e) { System.err.println("Error: " + e.getMessage()); if (isVerbose()) { e.printStackTrace(); } } System.exit(1); } private static void usage() { System.out.println("Usage:"); System.out.print(" " + Installer.class.getName()); System.out.println(" [-delete|-source |-register]"); System.out.println(" to create a service tag for the Java platform"); System.out.println(""); System.out.println("Internal Options:"); System.out.println(" -source: to specify the source of the service tag to be created"); System.out.println(" -delete: to delete the service tag "); System.out.println(" -register: to register the JDK"); System.out.println(" -help: to print this help message"); } }