| /******************************************************************************* |
| * Copyright (c) 2000, 2016 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.help.internal.standalone; |
| |
| import java.io.*; |
| import java.net.*; |
| import java.nio.channels.*; |
| |
| /** |
| * This program is used to start or stop Eclipse Infocenter application. It |
| * should be launched from command line. |
| */ |
| public class EclipseController implements EclipseLifeCycleListener { |
| public static final String CMD_INSTALL = "install"; //$NON-NLS-1$ |
| |
| public static final String CMD_UPDATE = "update"; //$NON-NLS-1$ |
| |
| public static final String CMD_ENABLE = "enable"; //$NON-NLS-1$ |
| |
| public static final String CMD_DISABLE = "disable"; //$NON-NLS-1$ |
| |
| public static final String CMD_UNINSTALL = "uninstall"; //$NON-NLS-1$ |
| |
| public static final String CMD_SEARCH = "search"; //$NON-NLS-1$ |
| |
| public static final String CMD_LIST = "listFeatures"; //$NON-NLS-1$ |
| |
| public static final String CMD_ADDSITE = "addSite"; //$NON-NLS-1$ |
| |
| public static final String CMD_REMOVESITE = "removeSite"; //$NON-NLS-1$ |
| |
| public static final String CMD_APPLY = "apply"; //$NON-NLS-1$ |
| |
| // control servlet path |
| private static final String CONTROL_SERVLET_PATH = "/help/control"; //$NON-NLS-1$ |
| |
| // application to launch |
| protected String applicationId; |
| |
| // Eclipse connection params |
| protected EclipseConnection connection; |
| |
| public Eclipse eclipse = null; |
| |
| // Inter process lock |
| private FileLock lock; |
| private RandomAccessFile raf; |
| |
| private boolean eclipseEnded = false; |
| |
| /** |
| * Constructs help system |
| * |
| * @param applicationId |
| * ID of Eclipse help application |
| * @param args |
| * array of String options and their values Option |
| * <code>-eclipseHome dir</code> specifies Eclipse installation |
| * directory. It must be provided, when current directory is not |
| * the same as Eclipse installation directory. Additionally, most |
| * options accepted by Eclipse execuable are supported. |
| */ |
| public EclipseController(String applicationId, String[] args) { |
| |
| this.applicationId = applicationId; |
| Options.init(applicationId, args); |
| connection = new EclipseConnection(); |
| } |
| |
| /** |
| * @see org.eclipse.help.standalone.Help#shutdown() |
| */ |
| public final synchronized void shutdown() throws Exception { |
| try { |
| obtainLock(); |
| sendHelpCommandInternal("shutdown", new String[0]); //$NON-NLS-1$ |
| } catch (MalformedURLException mue) { |
| mue.printStackTrace(); |
| } catch (InterruptedException ie) { |
| } finally { |
| releaseLock(); |
| } |
| } |
| |
| /** |
| * @see org.eclipse.help.standalone.Help#start() |
| */ |
| public final synchronized void start() throws Exception { |
| try { |
| obtainLock(); |
| startEclipse(); |
| } finally { |
| releaseLock(); |
| } |
| |
| } |
| |
| /** |
| * Ensures the application is running, and sends command to the control |
| * servlet. If connection fails, retries several times, in case webapp is |
| * starting up. |
| */ |
| protected final synchronized void sendHelpCommand(String command, |
| String[] parameters) throws Exception { |
| try { |
| obtainLock(); |
| sendHelpCommandInternal(command, parameters); |
| } finally { |
| releaseLock(); |
| } |
| |
| } |
| |
| /** |
| * Starts Eclipse if not yet running. |
| */ |
| private void startEclipse() throws Exception { |
| boolean fullyRunning = isApplicationRunning(); |
| if (fullyRunning) { |
| return; |
| } |
| if (Options.isDebug()) { |
| System.out |
| .println("Using workspace " + Options.getWorkspace().getAbsolutePath()); //$NON-NLS-1$ |
| } |
| // delete old connection file |
| Options.getConnectionFile().delete(); |
| connection.reset(); |
| |
| if (Options.isDebug()) { |
| System.out |
| .println("Ensured old .connection file is deleted. Launching Eclipse."); //$NON-NLS-1$ |
| } |
| eclipseEnded = false; |
| eclipse = new Eclipse(this); |
| eclipse.start(); |
| fullyRunning = isApplicationRunning(); |
| while (!eclipseEnded && !fullyRunning) { |
| try { |
| Thread.sleep(250); |
| } catch (InterruptedException ie) { |
| } |
| fullyRunning = isApplicationRunning(); |
| } |
| if (eclipseEnded) { |
| if (eclipse.getStatus() == Eclipse.STATUS_ERROR) { |
| throw eclipse.getException(); |
| } |
| return; |
| } |
| if (Options.isDebug()) { |
| System.out.println("Eclipse launched"); //$NON-NLS-1$ |
| } |
| // in case controller is killed |
| Runtime.getRuntime().addShutdownHook(new EclipseCleaner()); |
| } |
| |
| private void sendHelpCommandInternal(String command, String[] parameters) |
| throws Exception { |
| if (!"shutdown".equalsIgnoreCase(command)) { //$NON-NLS-1$ |
| startEclipse(); |
| } |
| if (!isApplicationRunning()) { |
| return; |
| } |
| if (!connection.isValid()) { |
| connection.renew(); |
| } |
| try { |
| String trustStoreLocation = Options.getTrustStoreLocation(); |
| if (trustStoreLocation != null) { |
| System.setProperty("javax.net.ssl.trustStore", trustStoreLocation); //$NON-NLS-1$ |
| } |
| String trustStorePassword = Options.getTrustStorePassword(); |
| if (trustStorePassword != null) { |
| System.setProperty("javax.net.ssl.trustStorePassword", trustStorePassword); //$NON-NLS-1$ |
| } |
| URL url = createCommandURL(command, parameters); |
| if ("shutdown".equalsIgnoreCase(command) //$NON-NLS-1$ |
| && Options.getConnectionFile().exists()) { |
| connection.connect(url); |
| long timeLimit = System.currentTimeMillis() + 60 * 1000; |
| while (Options.getConnectionFile().exists()) { |
| Thread.sleep(200); |
| if (System.currentTimeMillis() > timeLimit) { |
| System.out |
| .println("Shutting down is taking too long. Will not wait."); //$NON-NLS-1$ |
| break; |
| } |
| } |
| |
| } else { |
| connection.connect(url); |
| } |
| } catch (MalformedURLException mue) { |
| mue.printStackTrace(); |
| } catch (InterruptedException ie) { |
| } |
| } |
| |
| /** |
| * Builds a URL that communicates the specified command to help control |
| * servlet. |
| * |
| * @param command |
| * standalone help system command e.g. "displayHelp" |
| * @param parameters |
| * array of parameters of the command e.g. |
| * {"http://www.eclipse.org"} |
| */ |
| private URL createCommandURL(String command, String[] parameters) |
| throws MalformedURLException { |
| StringBuilder urlStr = new StringBuilder(); |
| urlStr.append("http://"); //$NON-NLS-1$ |
| urlStr.append(connection.getHost()); |
| urlStr.append(":"); //$NON-NLS-1$ |
| urlStr.append(connection.getPort()); |
| urlStr.append(CONTROL_SERVLET_PATH); |
| urlStr.append("?command="); //$NON-NLS-1$ |
| urlStr.append(command); |
| for (int i = 0; i < parameters.length; i++) { |
| urlStr.append("&"); //$NON-NLS-1$ |
| urlStr.append(parameters[i]); |
| } |
| if (Options.isDebug()) { |
| System.out.println("Control servlet URL=" + urlStr.toString()); //$NON-NLS-1$ |
| } |
| return new URL(urlStr.toString()); |
| } |
| |
| @Override |
| public void eclipseEnded() { |
| eclipseEnded = true; |
| connection.reset(); |
| } |
| |
| private void obtainLock() throws IOException { |
| if (lock != null) { |
| // we already have lock |
| return; |
| } |
| if (!Options.getLockFile().exists()) { |
| Options.getLockFile().getParentFile().mkdirs(); |
| } |
| raf = new RandomAccessFile(Options.getLockFile(), "rw"); //$NON-NLS-1$ |
| lock = raf.getChannel().lock(); |
| if (Options.isDebug()) { |
| System.out.println("Lock obtained."); //$NON-NLS-1$ |
| } |
| } |
| |
| private void releaseLock() { |
| if (lock != null) { |
| try { |
| lock.channel().close(); |
| if (Options.isDebug()) { |
| System.out.println("Lock released."); //$NON-NLS-1$ |
| } |
| lock = null; |
| } catch (IOException ioe) { |
| } |
| } |
| if (raf != null) { |
| try { |
| raf.close(); |
| } catch (IOException ioe) { |
| } |
| raf = null; |
| } |
| } |
| |
| /** |
| * Tests whether HelpApplication is running by testing if .applicationlock |
| * is locked |
| */ |
| private boolean isApplicationRunning() { |
| File applicationLockFile = new File(Options.getLockFile() |
| .getParentFile(), ".applicationlock"); //$NON-NLS-1$ |
| FileLock applicationLock = null; |
| try (RandomAccessFile randomAccessFile = new RandomAccessFile(applicationLockFile, "rw")) { //$NON-NLS-1$ |
| |
| applicationLock = randomAccessFile.getChannel().tryLock(); |
| } catch (IOException ioe) { |
| } finally { |
| if (applicationLock != null) { |
| try { |
| applicationLock.release(); |
| } catch (IOException ioe) { |
| } |
| } |
| if (Options.isDebug()) { |
| System.out |
| .println("isApplicationRunning? " + (applicationLock == null)); //$NON-NLS-1$ |
| } |
| } |
| return applicationLock == null; |
| } |
| |
| public class EclipseCleaner extends Thread { |
| @Override |
| public void run() { |
| if (eclipse != null) { |
| eclipse.killProcess(); |
| } |
| } |
| } |
| |
| /** |
| * @return true if commands contained a known command and it was executed |
| */ |
| protected boolean executeUpdateCommand(String updateCommand) |
| throws Exception { |
| String[] parameters = Options.getUpdateParameters(); |
| sendHelpCommandInternal(updateCommand, parameters); |
| return true; |
| } |
| } |