/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.start.common; import java.io.PrintStream; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.Future; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.eclipse.equinox.app.IApplication; import de.rcenvironment.core.command.api.CommandExecutionResult; import de.rcenvironment.core.command.api.CommandExecutionService; import de.rcenvironment.core.command.common.CommandException; import de.rcenvironment.core.start.common.validation.api.InstanceValidationResult; import de.rcenvironment.core.start.common.validation.api.InstanceValidationResult.InstanceValidationResultType; import de.rcenvironment.core.start.common.validation.api.InstanceValidationService; import de.rcenvironment.core.utils.common.StringUtils; import de.rcenvironment.core.utils.common.VersionUtils; import de.rcenvironment.core.utils.common.textstream.TextOutputReceiver; /** * Abstract base class for "instance runners". A single implementation is invoked by the main application to perform startup steps that are * specific to either GUI or headless mode. * * The concrete implementation is injected into {@link Instance} by specific launcher bundles. This avoids dependencies from the main * application to GUI code, which would trigger GUI bundles to start in headless mode. * * @author Robert Mischke * @author Doreen Seider * @author David Scholz (minor change: added check for shutdown request) */ public abstract class InstanceRunner { protected static volatile InstanceValidationService instanceValidationService; private static volatile CommandExecutionService commandExecutionService; protected final Log log = LogFactory.getLog(getClass()); /** * Injects the {@link CommandExecutionService} instance to use for dispatching commands. * * @param newService the new instance */ public void bindCommandExecutionService(CommandExecutionService newService) { InstanceRunner.commandExecutionService = newService; } /** * Injects the {@link InstanceValidationService} instance to validate the RCE instance on startup. * * @param newService the new instance */ public void bindInstanceValidationService(InstanceValidationService newService) { InstanceRunner.instanceValidationService = newService; } /** * Invokes this runner. * * @return the return code for the main application. * @throws Exception on uncaught exceptions */ public int run() throws Exception { // Write versions to log file log.debug("Core version: " + VersionUtils.getVersionOfCoreBundles()); log.debug("Product version: " + VersionUtils.getVersionOfProduct()); log.debug("Command line arguments passed: " + System.getProperty("sun.java.command")); RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean(); for (String vmArg : runtime.getInputArguments()) { log.debug("JVM argument passed: " + vmArg); } if (Instance.isShutdownRequested()) { return IApplication.EXIT_OK; } if (!validateInstance()) { return IApplication.EXIT_OK; } else { return performRun(); } } private boolean validateInstance() { Map<InstanceValidationResultType, List<InstanceValidationResult>> validationResults = instanceValidationService.validateInstance(); int passed = validationResults.get(InstanceValidationResultType.PASSED).size(); int failedWithProceedingAllowed = validationResults.get(InstanceValidationResultType.FAILED_PROCEEDING_ALLOWED).size(); int failedWithShutdownRequired = validationResults.get(InstanceValidationResultType.FAILED_SHUTDOWN_REQUIRED).size(); log.debug(StringUtils.format("Instance validation results [%d in total]: %d passed, %d failed with proceeding allowed, " + "%d failed with shutdown required", passed + failedWithProceedingAllowed + failedWithShutdownRequired, passed, failedWithProceedingAllowed, failedWithShutdownRequired)); if (validationResults.containsKey(InstanceValidationResultType.FAILED_PROCEEDING_ALLOWED)) { for (InstanceValidationResult result : validationResults.get(InstanceValidationResultType.FAILED_PROCEEDING_ALLOWED)) { log.error(StringUtils.format("Instance validation '%s' failed: %s", result.getValidationDisplayName(), result.getLogMessage())); } } if (validationResults.containsKey(InstanceValidationResultType.FAILED_SHUTDOWN_REQUIRED)) { for (InstanceValidationResult result : validationResults.get(InstanceValidationResultType.FAILED_SHUTDOWN_REQUIRED)) { log.error(StringUtils.format("Instance validation '%s' failed: %s. RCE is shutting down", result.getValidationDisplayName(), result.getLogMessage())); } } if (failedWithProceedingAllowed > 0 || failedWithShutdownRequired > 0) { return onInstanceValidationFailures(validationResults); } return true; } /** * Runs the instance. Subclasses need to implement. Common run logic goes into {@link #run()}. * * @return the return code for the main application. * @throws Exception on uncaught exceptions */ public abstract int performRun() throws Exception; /** * May (optionally) present user feedback about startup instance validation failures. * * @param validationResults result of the instance validation * @return <code>false</code> if instance validation failed and RCE must be shut down, otherwise <code>false</code> */ // TODO refactor to avoid validation-specific parameter? public boolean onInstanceValidationFailures(Map<InstanceValidationResultType, List<InstanceValidationResult>> validationResults) { if (validationResults.get(InstanceValidationResultType.FAILED_SHUTDOWN_REQUIRED).size() > 0) { return false; } return true; } /** * Custom hook that is fired before the common code of {@link Instance#awaitShutdown()}. */ public void beforeAwaitShutdown() {} /** * Performs custom actions when {@link Instance#shutdown()} is called. */ public void triggerShutdown() {} /** * Restarts RCE. */ public void triggerRestart() {} protected Future<CommandExecutionResult> initiateAsyncCommandExecution(final String[] execCommandTokens, final String taskDescription, final boolean isBatchMode) { if (commandExecutionService == null) { log.error("Command execution service not available; ignoring provided command(s) " + execCommandTokens); return null; } final String taskDescriptionWithTokens = taskDescription + " (\"" + org.apache.commons.lang3.StringUtils.join(execCommandTokens, " ") + "\")"; final PrintStream stdout = System.out; TextOutputReceiver outputReceiver = new TextOutputReceiver() { @Override public void addOutput(String line) { if (isBatchMode) { log.debug("Output of command-line batch command(s): " + line); stdout.println(line); } else { log.info("Output of command-line startup command(s): " + line); } } @Override public void onStart() { log.debug("Starting " + taskDescriptionWithTokens); } @Override public void onFatalError(Exception e) { CommandException ce = (CommandException) e; if (ce.getType().equals(CommandException.Type.HELP_REQUESTED)) { stdout.println(commandExecutionService.getHelpText(false, ce.shouldPrintDeveloperHelp())); } else { log.error("Error during " + taskDescription, e); } } @Override public void onFinished() { log.debug("Finished " + taskDescriptionWithTokens); } }; return commandExecutionService.asyncExecMultiCommand(Arrays.asList(execCommandTokens), outputReceiver, taskDescription); } }