/* VisAD system for interactive analysis and visualization of numerical data. Copyright (C) 1996 - 2017 Bill Hibbard, Curtis Rueden, Tom Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and Tommy Jasmin. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ package visad.install; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.jar.JarEntry; import java.util.jar.JarFile; import javax.swing.JOptionPane; import visad.util.CmdlineGenericConsumer; import visad.util.CmdlineParser; class ClusterInstaller { private Runtime runtime = null; private String[] argList = null; private String cPush; public ClusterInstaller(String cPush) { this.cPush = cPush; } public Process push(String target) throws IOException { return push(target, target); } public Process push(String source, String target) throws IOException { if (runtime == null) { runtime = Runtime.getRuntime(); } if (argList == null) { argList = new String[] { cPush, null, null }; } argList[1] = source; argList[2] = target; return runtime.exec(argList); } } public class Main extends CmdlineGenericConsumer { private static final String CLASSPATH_PROPERTY = "java.class.path"; private static final String ARCH_PROPERTY = "visad.install.arch"; private static final String HOME_PROPERTY = "visad.install.home"; private static final String PATH_PROPERTY = "visad.install.path"; private static final String SPLASH_NAME = "visad-splash.jpg"; private static final String JAR_NAME = "visad.jar"; private static final String VISAD_JAR_URL = "ftp://ftp.ssec.wisc.edu/pub/visad-2.0/" + JAR_NAME; private boolean debug; private URL jarURL; private ChooserList chooser; private Path classpath, path; private ArrayList jarList, javaList; private File installerJar; private JavaFile installerJava; private File installerJavaDir, installerJavaJar; private String archStr; private String cPushStr; private boolean useSuppliedJava, downloadLatestJar; // private File jvmToUse; private File javaInstallDir, jarInstallDir; private ClusterInstaller clusterInstaller; public Main(String[] args) { CmdlineParser cmdline = new CmdlineParser(this); if (!cmdline.processArgs(args)) { System.exit(1); return; } File distDir; // build File object from distribution directory property String ddStr = System.getProperty(HOME_PROPERTY); if (ddStr == null) { distDir = null; } else { distDir = new File(ddStr); } // if no distribution directory, use current directory if (distDir == null || !distDir.exists()) { distDir = new File("."); } SplashScreen ss = null; File splashFile = new File(distDir, SPLASH_NAME); if (splashFile.exists()) { ss = new SplashScreen(getPath(splashFile)); ss.setVisible(true); } boolean initResult = initialize(distDir); if (ss != null) { ss.setVisible(false); } if (!initResult) { System.exit(1); return; } if (debug) { dumpInitialState(distDir); } useSuppliedJava = downloadLatestJar = false; // jvmToUse = null; javaInstallDir = jarInstallDir = null; clusterInstaller = null; queryUser(); if (debug) { dumpInstallState(); } install(); } /** * Check all java executables for minimum version. * * @param list list of File objects * @param major major number of java version * @param minor minor number of java version */ private static final void checkJavaVersions(ArrayList list, int major, int minor) { int i = 0; while (i < list.size()) { JavaFile f = new JavaFile((File )list.get(i)); if (f.matchMinimum(major, minor)) { list.set(i++, f); } else { list.remove(i); } } } public int checkOption(String mainName, char ch, String arg) { if (ch == 'x') { debug = true; return 1; } return 0; } /** * Push installed files out to cluster. * * @param mon progress monitor (ignored if <tt>null</tt>.) * @param source source file/directory * @param target directory on cluster machine where source is installed. */ private final void clusterPush(ProgressMonitor mon, String source, String target) { Process p; try { p = clusterInstaller.push(source, target); } catch (IOException ioe) { ioe.printStackTrace(); return; } try { p.getOutputStream().close(); } catch (IOException ioe) { } BufferedReader in,err; in = new BufferedReader(new InputStreamReader(p.getInputStream())); err = new BufferedReader(new InputStreamReader(p.getErrorStream())); // !! WARNING !! // // This loop will hang if too much output is sent to 'err' // without anything being sent to 'in' // boolean looping = true; while (looping) { if (in != null) { try { String line = in.readLine(); if (line == null) { in = null; looping = (err != null); } else if (mon != null) { mon.setDetail(line); } } catch (IOException ioe) { ioe.printStackTrace(); break; } } if (err != null) { try { if (err.ready() || in == null) { String line = err.readLine(); if (line == null) { err = null; looping = (in != null); } else if (mon != null) { mon.setDetail(line); } } } catch (IOException ioe) { ioe.printStackTrace(); break; } } } try { p.waitFor(); } catch (InterruptedException ie) { ie.printStackTrace(); } } /** * Ugly hack to make JVM binaries executable. */ private final void makeJVMBinariesExecutable() { // if we don't know the path, we can't find the executable if (path == null) { return; } // find all instances of 'chmod' ArrayList chmodList = path.find("chmod"); if (chmodList == null || chmodList.size() == 0) { // if no 'chmod' found, we're done return; } // only care about one of them, so arbitrarily grab the first one File chmod = (File )chmodList.get(0); // get a handle for the JVM executable directory File jvmBin = new File(javaInstallDir, "bin"); if (!jvmBin.exists()) { // if JVM binary directory doesn't exist, we're done return; } // get the list of JVM executables String[] binFiles = jvmBin.list(); if (binFiles == null || binFiles.length == 0) { // if JVM binary directory is empty, we're done return; } // create an array holding the command to be executed String[] cmd = new String[2 + binFiles.length]; cmd[0] = chmod.toString(); cmd[1] = "555"; // preload the directory path into the stringbuffer StringBuffer buf = new StringBuffer(jvmBin.toString()); buf.append('/'); // remember the buffer length final int len = buf.length(); // add all executables to the command list for (int i = 0; i < binFiles.length; i++) { buf.setLength(len); buf.append(binFiles[i]); cmd[i+2] = buf.toString(); } Process p; try { p = Runtime.getRuntime().exec(cmd); } catch (IOException ioe) { ioe.printStackTrace(); return; } try { p.waitFor(); } catch (InterruptedException ie) { ie.printStackTrace(); } } /** * Pop up directory chooser dialog and process user's response. * * @param chooser previously created directory chooser window * @param list list of directory choices * @param title directory chooser window title */ private final static File chooseDirectory(ChooserList chooser, ArrayList list, String title) { final int listLen = (list == null ? 0 : list.size()); boolean allDirs = true; for (int i = 0; i < listLen; i++) { File f = (File )list.get(i); if (!f.isDirectory()) { allDirs = false; break; } } File[] dList; if (!allDirs) { // build list containing only directories dList = new File[listLen]; for (int i = 0; i < listLen; i++) { File f = (File )list.get(i); if (f.isDirectory()) { dList[i] = f; } else { dList[i] = new File(f.getParent()); } } } else if (listLen > 0) { // build array from ArrayList dList = (File[] )list.toArray(new File[list.size()]); } else { // empty/null list dList = null; } chooser.setList(dList); chooser.setFileSelectionMode(ChooserList.DIRECTORIES_ONLY); chooser.setDialogTitle(title); chooser.setApproveButtonToolTipText("Select directory"); chooser.setApproveButtonText("Select..."); int option = chooser.showDialog(null, "Select directory"); if (option == ChooserList.CANCEL_OPTION) { return null; } File choice = chooser.getSelectedFile(); if (!choice.exists()) { return choice; } if (choice.isDirectory()) { return choice; } return new File(choice.getParent()); } /** * Pop up file chooser dialog and process user's response. * * @param chooser previously created file chooser window * @param list list of file choices * @param title file chooser window title */ private static final File chooseFile(ChooserList chooser, ArrayList list, String title) { if (list == null) { chooser.setList(null); } else { chooser.setList((File[] )list.toArray(new File[list.size()])); } chooser.setFileSelectionMode(ChooserList.FILES_ONLY); chooser.setDialogTitle(title); chooser.setApproveButtonToolTipText("Choose file"); int option = chooser.showDialog(null, "Choose file"); if (option == ChooserList.CANCEL_OPTION) { return null; } return chooser.getSelectedFile(); } /** * Dump post-initialization state for debugging */ private final void dumpInitialState(File distDir) { if (distDir != null && distDir.exists()) { System.out.println("Distribution directory: " + distDir); } else { System.out.println("Distribution directory: UNKNOWN!"); } if (installerJavaDir != null) { System.out.println("Supplied java directory: " + installerJavaDir); } if (installerJavaJar != null) { System.out.println("Supplied java jar file: " + installerJavaJar); } if (installerJava != null) { System.out.println("Supplied java: " + installerJava); } if (installerJar != null) { System.out.println("Supplied visad.jar: " + installerJar); } if (jarList == null || jarList.size() == 0) { System.err.println("No " + JAR_NAME + " found in " + classpath); } else { System.err.println("== jar file list =="); for (int i = 0; i < jarList.size(); i++) { System.out.println("#" + i + ": " + getPath((File )jarList.get(i))); } } if (javaList == null || javaList.size() == 0) { System.err.println("No java executable found in path " + path); } else { System.err.println("== java executable list =="); for (int i = 0; i < javaList.size(); i++) { System.out.println("#" + i + ": " + getPath((File )javaList.get(i))); } } if (cPushStr == null) { System.err.println("No cluster executable found in path " + path); } else { System.err.println("== cluster executable =="); System.out.println(cPushStr); } } /** * Dump pre-installation state for debugging. */ private final void dumpInstallState() { if (useSuppliedJava) { System.err.println("Install java in " + javaInstallDir); // if (jvmToUse != null) { // System.err.println("!! 'jvmToUse' is set !!"); // } // } else if (jvmToUse != null) { // System.err.println("Use jvm in " + jvmToUse); // if (javaInstallDir != null) { // System.err.println("!! 'javaInstallDir' is set !!"); // } } if (downloadLatestJar) { System.err.println("Download latest " + JAR_NAME); } System.err.println("Install " + JAR_NAME + " in " + jarInstallDir); if (clusterInstaller != null) { System.err.println("Push installed files out to cluster"); } } /** * Remove and return the installer executable from the * list of java executable JavaFile objects. * * @param list list of JavaFile objects * * @return <tt>null</tt> if installer-supplied java executable * was not found */ private static final File extractInstallerFile(File distDir, ArrayList javaList) { String distPath = getPath(distDir); java.util.Iterator iter = javaList.iterator(); while (iter.hasNext()) { File thisFile = (File )iter.next(); if (getPath(thisFile).startsWith(distPath)) { iter.remove(); return thisFile; } } return null; } /** * Return either the canonical path or, if that is not possible, * the specified path. * * @param f File object * * @return either the canonical path or the originally specified path. */ private static final String getPath(File f) { try { return f.getCanonicalPath(); } catch (IOException ioe) { return f.getPath(); } } /** * Initialize internal state. */ private final boolean initialize(File distDir) { // create a ChooserList (for speed purposes) chooser = new ChooserList(); // set everything to null classpath = path = null; jarURL = null; jarList = javaList = null; // get class path elements try { classpath = new Path(System.getProperty(CLASSPATH_PROPERTY)); } catch (IllegalArgumentException iae) { System.err.println(getClass().getName() + ": Couldn't get Java class path"); return false; } // get executable path elements String pathStr = System.getProperty(PATH_PROPERTY); if (pathStr == null) { path = null; } else { try { path = new Path(pathStr); } catch (IllegalArgumentException iae) { path = null; } } // build the URL for the jar file try { jarURL = new URL(VISAD_JAR_URL); } catch (java.net.MalformedURLException mue) { jarURL = null; } // no installer-supplied jar file found yet installerJar = null; // find all visad jar files jarList = classpath.findMatch(JAR_NAME); if (jarList != null) { loseDuplicates(jarList); installerJar = extractInstallerFile(distDir, jarList); } // die if we can't install anything if (installerJar == null && jarURL == null) { System.err.println("Couldn't find either distributed jar file" + " or jar file URL!"); System.exit(1); } // no installer-supplied java found yet installerJava = null; installerJavaDir = null; // find all java executables if (path == null) { javaList = null; } else { javaList = path.find("java"); if (javaList != null) { loseDuplicates(javaList); checkJavaVersions(javaList, 1, 2); installerJava = (JavaFile )extractInstallerFile(distDir, javaList); if (installerJava != null && installerJava.getName().equals("java")) { File canonJava = new File(getPath(installerJava)); installerJavaDir = new File(canonJava.getParent()); if (installerJavaDir.getName().equals("bin")) { installerJavaDir = new File(installerJavaDir.getParent()); } } } } // fetch the architecture string archStr = System.getProperty(ARCH_PROPERTY); if (archStr != null && archStr.length() == 0) { archStr = null; } // no installer-supplied java jar file found yet installerJavaJar = null; // if no supplied java dir was found, look for java jar file if (installerJavaDir == null && archStr != null) { File tmpFile = new File(distDir, "jdk-" + archStr + ".jar"); if (tmpFile.exists()) { installerJavaJar = tmpFile; } } // no cluster installation executable found yet cPushStr = null; // find all cluster installation executables if (path != null) { ArrayList c3List = path.find("cpush"); if (c3List != null) { loseDuplicates(c3List); if (c3List != null && c3List.size() > 0) { cPushStr = getPath((File )c3List.get(0)); } } } return true; } public void initializeArgs() { debug = false; } /** * Install VisAD. */ private final void install() { ProgressMonitor mon = new ProgressMonitor(); mon.setPhase("Starting install"); mon.setVisible(true); // install jar if (downloadLatestJar) { mon.setPhase("Downloading jar file"); Download.getFile(jarURL, jarInstallDir, false); } else { mon.setPhase("Copying jar file"); Util.copyFile(mon, installerJar, jarInstallDir, ".old"); } // if they want a cluster install, push the jar file out to the nodes if (clusterInstaller != null) { mon.setPhase("Pushing jar file to cluster"); clusterPush(null, getPath(new File(jarInstallDir, JAR_NAME)), getPath(jarInstallDir)); } // install JVM if (useSuppliedJava) { mon.setPhase("Copying JVM"); if (installerJavaDir != null) { // install unpacked JVM if (javaInstallDir.exists()) { javaInstallDir = new File(javaInstallDir, installerJavaDir.getName()); } Util.copyDirectory(mon, installerJavaDir, javaInstallDir); } else { // install JVM from jar file String jarTop = getJarTopDir(installerJavaJar); if (jarTop == null && archStr != null) { jarTop = "jdk-" + archStr; } if (jarTop != null) { javaInstallDir = new File(javaInstallDir, jarTop); } Util.copyJar(mon, installerJavaJar, javaInstallDir); } mon.setPhase("Setting JVM executable bits"); makeJVMBinariesExecutable(); // if they want a cluster install, push the JVM out to the nodes if (clusterInstaller != null) { mon.setPhase("Pushing JVM to cluster"); clusterPush(mon, javaInstallDir.toString(), javaInstallDir.getParent().toString()); } } // all done mon.setPhase("Install finished!"); try { Thread.sleep(2000); } catch (InterruptedException ie) { } } /** * Return the top directory contained in the specified jar file * * @param source jar file to examine * * @return either the sole top-level directory in the jar file * or <tt>null</tt> if the jar file contains multiple * top-level files/directories. */ private String getJarTopDir(File source) { // try to open the jar file JarFile jar; try { jar = new JarFile(source); } catch (IOException ioe) { return null; } String topDir = null; Enumeration en = jar.entries(); while (en.hasMoreElements()) { JarEntry entry = (JarEntry )en.nextElement(); String entryName = entry.getName(); // skip manifest files if (JarFile.MANIFEST_NAME.startsWith(entryName)) { continue; } // get the top directory for this entry String dirName; int dirIdx = entryName.indexOf(File.separatorChar); if (dirIdx < 0) { dirName = entryName; } else { dirName = entryName.substring(0, dirIdx); } if (topDir == null) { // if we haven't seen a top-level directory, save this topDir = dirName; } else if (!topDir.equals(dirName)) { // we already have a different to-level dir, so give up topDir = null; break; } } return topDir; } /** * Remove duplicate objects from the list. * * @param list of Objects */ private static final void loseDuplicates(ArrayList list) { for (int i = 0; i < list.size(); i++) { Object objI = list.get(i); int j = i + 1; while (j < list.size()) { Object objJ = list.get(j); if (!objI.equals(objJ)) { j++; } else { list.remove(j); } } } } public String optionUsage() { return super.optionUsage() + " [-x(debug)]"; } /** * Query user about installation options. */ private final void queryUser() { final int STEP_INSTALL_JAR = 0; final int STEP_DOWNLOAD_JAR = 1; final int STEP_USE_SUPPLIED = 2; final int STEP_INSTALL_JAVA = 3; final int STEP_CLUSTER = 4; final int STEP_FINISHED = 5; int step = 0; while (step < STEP_FINISHED) { switch (step) { case STEP_USE_SUPPLIED: if (queryUserUseSuppliedJava()) { step++; } else { step--; } break; case STEP_INSTALL_JAVA: step += queryUserInstallJava(); break; case STEP_INSTALL_JAR: step += queryUserInstallJar(); break; case STEP_DOWNLOAD_JAR: if (queryUserDownloadJar()) { step++; } else { step--; } break; case STEP_CLUSTER: if (queryUserClusterPush()) { step++; } else { step--; } break; } if (step < 0) { if (queryUserCancelInstall()) { System.exit(0); return; } // don't go negative step = 0; } } } /** * Ask user if they want to cancel the install. * * @return <tt>true</tt> if user wants to cancel. */ private final boolean queryUserCancelInstall() { String canMsg = "Do you want to cancel this install?"; int n = JOptionPane.showConfirmDialog(null, canMsg, "Cancel install?", JOptionPane.YES_NO_OPTION); return (n == JOptionPane.YES_OPTION); } /** * Ask user if they want to install everything on the cluster. * * @return false if [Cancel] button was pressed, * true if another choice was made. */ private final boolean queryUserClusterPush() { int result = JOptionPane.NO_OPTION; if (cPushStr != null) { String msg = "Would you like to push everything out to the cluster?"; String title = "Push files to cluster?"; result = JOptionPane.showConfirmDialog(null, msg, title, JOptionPane.YES_NO_CANCEL_OPTION); } if (result != JOptionPane.YES_OPTION) { clusterInstaller = null; } else { clusterInstaller = new ClusterInstaller(cPushStr); } return (result != JOptionPane.CANCEL_OPTION); } /** * Ask user if they want to download the latest visad.jar * * @return false if [Cancel] button was pressed, * true if another choice was made. */ private final boolean queryUserDownloadJar() { int result = JOptionPane.YES_OPTION; if (installerJar != null) { String msg = "Would you like to download the latest " + JAR_NAME + "?"; String title = "Download latest " + JAR_NAME + "?"; result = JOptionPane.showConfirmDialog(null, msg, title, JOptionPane.YES_NO_CANCEL_OPTION); } downloadLatestJar = (result == JOptionPane.YES_OPTION); return (result != JOptionPane.CANCEL_OPTION); } /** * Ask user to specify the directory in which the visad.jar file * should be installed. * * @return -1 if [Cancel] button was pressed, * 0 if a bad directory was selected, * 1 if a valid choice was made. */ private final int queryUserInstallJar() { jarInstallDir = chooseDirectory(chooser, jarList, "Select the directory where the VisAD jar file should be installed"); if (jarInstallDir == null) { return -1; } if (!jarInstallDir.canWrite()) { JOptionPane.showMessageDialog(null, "Cannot write to that directory!", "Bad directory?", JOptionPane.ERROR_MESSAGE); return 0; } return 1; } /** * Ask user to specify the directory in which the supplied * JDK should be installed. * * @return -1 if [Cancel] button was pressed, * 0 if a bad directory was selected, * 1 if a valid choice was made. */ private final int queryUserInstallJava() { javaInstallDir = null; // jvmToUse = null; if (useSuppliedJava) { javaInstallDir = chooseDirectory(chooser, null, "Select the directory in which the JDK should be installed"); if (javaInstallDir == null) { return -1; } if (!javaInstallDir.canWrite()) { JOptionPane.showMessageDialog(null, "Cannot write to that directory!", "Bad directory?", JOptionPane.ERROR_MESSAGE); return 0; } // } else { // jvmToUse = chooseFile(chooser, javaList, // "Select the java program to use"); // if (jvmToUse == null) { // return -1; // } } return 1; } /** * Ask user if they'd like to have the supplied JDK installed. * * @return false if [Cancel] button was pressed, * true if another choice was made. */ private final boolean queryUserUseSuppliedJava() { int result = JOptionPane.NO_OPTION; if (installerJavaDir != null || installerJavaJar != null) { String msg; if (installerJavaDir != null) { msg = "Would you like to install the supplied " + " Java Development Kit " + installerJava.getMajor() + "." + installerJava.getMinor() + " (" + installerJava.getVersionString() + ")?"; } else { msg = "Would you like to install the supplied " + " Java Development Kit?"; } String title = "Install supplied JDK?"; result = JOptionPane.showConfirmDialog(null, msg, title, JOptionPane.YES_NO_CANCEL_OPTION); } useSuppliedJava = (result == JOptionPane.YES_OPTION); return (result != JOptionPane.CANCEL_OPTION); } public static final void main(String[] args) { new Main(args); System.exit(0); } }