interactivespaces.liveactivity.runtime.standalone.development.DevelopmentStandaloneLiveActivityRuntime.java Source code

Java tutorial

Introduction

Here is the source code for interactivespaces.liveactivity.runtime.standalone.development.DevelopmentStandaloneLiveActivityRuntime.java

Source

/*
 * Copyright (C) 2015 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package interactivespaces.liveactivity.runtime.standalone.development;

import interactivespaces.InteractiveSpacesException;
import interactivespaces.activity.ActivityStatus;
import interactivespaces.activity.component.route.MessageRouterActivityComponent;
import interactivespaces.configuration.Configuration;
import interactivespaces.evaluation.ExpressionEvaluatorFactory;
import interactivespaces.evaluation.SimpleExpressionEvaluatorFactory;
import interactivespaces.liveactivity.runtime.LiveActivityRuntimeComponentFactory;
import interactivespaces.liveactivity.runtime.LiveActivityRuntimeListener;
import interactivespaces.liveactivity.runtime.LiveActivityStatusPublisher;
import interactivespaces.liveactivity.runtime.SimpleLiveActivityFilesystem;
import interactivespaces.liveactivity.runtime.StandardLiveActivityRuntime;
import interactivespaces.liveactivity.runtime.alert.LoggingAlertStatusManager;
import interactivespaces.liveactivity.runtime.domain.InstalledLiveActivity;
import interactivespaces.liveactivity.runtime.installation.ActivityInstallationManager;
import interactivespaces.liveactivity.runtime.logging.InteractiveSpacesEnvironmentLiveActivityLogFactory;
import interactivespaces.liveactivity.runtime.logging.LiveActivityLogFactory;
import interactivespaces.liveactivity.runtime.standalone.StandaloneActivityInstallationManager;
import interactivespaces.liveactivity.runtime.standalone.StandaloneLiveActivityInformation;
import interactivespaces.liveactivity.runtime.standalone.StandaloneLiveActivityInformationCollection;
import interactivespaces.liveactivity.runtime.standalone.StandaloneLiveActivityStorageManager;
import interactivespaces.liveactivity.runtime.standalone.StandaloneLocalLiveActivityRepository;
import interactivespaces.liveactivity.runtime.standalone.messaging.StandaloneMessageRouter;
import interactivespaces.system.InteractiveSpacesEnvironment;
import interactivespaces.util.concurrency.SequentialEventQueue;
import interactivespaces.util.concurrency.SimpleSequentialEventQueue;
import interactivespaces.util.io.FileSupport;
import interactivespaces.util.io.FileSupportImpl;
import interactivespaces.util.resource.ManagedResource;
import interactivespaces.util.resource.ManagedResources;

import com.google.common.collect.Lists;

import org.apache.commons.logging.Log;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.channels.FileLock;
import java.util.List;
import java.util.concurrent.Future;

/**
 * A standalone runner for activities that takes the activities from a development environment.
 *
 * @author Trevor Pering
 * @author Keith M. Hughes
 */
public class DevelopmentStandaloneLiveActivityRuntime implements ManagedResource {

    /**
     * The path relative to an activity project folder where development information is kept.
     */
    public static final String ACTIVITY_PATH_DEVELOPMENT = "dev";

    /**
     * The path where the build staging directory is.
     *
     * TODO(keith): See how much workbench info could be placed in a common library and move this in there.
     */
    public static final String ACTIVITY_PATH_BUILD_STAGING = "build/staging";

    /**
     * Config parameter for the instance suffix.
     */
    public static final String CONFIGURATION_INTERACTIVESPACES_STANDALONE_INSTANCE = "interactivespaces.standalone.instance";

    /**
     * Config parameter for activity runtime.
     */
    public static final String CONFIGURATION_INTERACTIVESPACES_STANDALONE_ACTIVITY_RUNTIME = "interactivespaces.standalone.activity.runtime";

    /**
     * Config parameter for activity source.
     */
    public static final String CONFIGURATION_INTERACTIVESPACES_STANDALONE_ACTIVITY_SOURCE = "interactivespaces.standalone.activity.source";

    /**
     * Config parameter for activity config.
     */
    public static final String CONFIGURATION_INTERACTIVESPACES_STANDALONE_ACTIVITY_CONFIG = "interactivespaces.standalone.activity.config";

    /**
     * Config parameter for router type.
     */
    public static final String CONFIGURATION_INTERACTIVESPACES_STANDALONE_ROUTER_TYPE = "interactivespaces.standalone.router.type";

    /**
     * Mode value for standalone controller mode.
     */
    public static final String CONFIGURATION_VALUE_CONTROLLER_MODE_STANDALONE = "standalone";

    /**
     * The file filter for finding directories.
     */
    private static final FileFilter DIRECTORY_FILE_FILTER = new FileFilter() {
        @Override
        public boolean accept(File pathname) {
            return pathname.isDirectory();
        }
    };

    /**
     * The collection of information about the live activities to run.
     */
    private StandaloneLiveActivityInformationCollection liveActivityInformation = new StandaloneLiveActivityInformationCollection();

    /**
     * The component factory for the live activity runtime.
     */
    private LiveActivityRuntimeComponentFactory runtimeComponentFactory;

    /**
     * The internal managed resources for the runner.
     */
    private ManagedResources managedResources;

    /**
     * Number of threads in the thread pool.
     */
    public static final int THREAD_POOL_SIZE = 100;

    /**
     * Config filename to use for activity specific configuration.
     */
    public static final String ACTIVITY_SPECIFIC_CONFIG_FILE_NAME = "standalone.conf";

    /**
     * Config filename to use for activity specific configuration.
     */
    public static final String LOCAL_CONFIG_FILE_NAME = "local.conf";

    /**
     * Config filename for the included activity configuration.
     */
    public static final String ACTIVITY_CONFIG_FILE_NAME = "activity.conf";

    /**
     * Filename to use for a running instance lock.
     */
    private static final String LOCK_FILE_NAME = "instancelock";

    /**
     * Maximum number of standalone instances to allow.
     */
    private static final int MAX_INSTANCE_COUNT = 10;

    /**
     * File support instance to use.
     */
    private final FileSupport fileSupport = FileSupportImpl.INSTANCE;

    /**
     * Instance suffix for handling multiple instances run in the same directory.
     */
    private String instanceSuffix;

    /**
     * {@code true} if this is the primary (first) instance running in the same directory.
     */
    private boolean isPrimaryInstance;

    /**
     * {@code true} (default) if the standalone router should be used.
     */
    private boolean useStandaloneRouter = true;

    /**
     * Path name for trace filters, if any.
     */
    private String traceFilterPath;

    /**
     * Path name for trace playback file.
     */
    private String tracePlaybackPath;

    /**
     * Path name for trace check file.
     */
    private String traceCheckPath;

    /**
     * Path name for send path.
     */
    private String traceSendPath;

    /**
     * Mutable local config file for this activity. Has default, but can be set.
     */
    private File localConfigFile = new File(LOCAL_CONFIG_FILE_NAME);

    /**
     * Mutable local config file for this activity. Has default, but can be set.
     */
    private File activityConfigFile = new File(ACTIVITY_SPECIFIC_CONFIG_FILE_NAME);

    /**
     * Message router to use for this standalone activity.
     */
    private StandaloneMessageRouter cecRouter;

    /**
     * Space environment.
     */
    private final InteractiveSpacesEnvironment spaceEnvironment;

    /**
     * The live activity runtime for this runner.
     */
    private StandardLiveActivityRuntime liveActivityRuntime;

    /**
     * The live activity runtime listener.
     */
    private LiveActivityRuntimeListener liveActivityRuntimeListener;

    /**
     * Publisher for live activity statuses.
     */
    private LiveActivityStatusPublisher liveActivityStatusPublisher = new LiveActivityStatusPublisher() {

        @Override
        public void publishActivityStatus(String uuid, ActivityStatus status) {
            onPublishActivityStatus(uuid, status);
        }
    };

    /**
     * The future for running the activities.
     */
    private Future<?> playerFuture;

    /**
     * Construct a new standalone runtime.
     *
     * @param runtimeComponentFactory
     *          component factory for the live activity runtime
     * @param spaceEnvironment
     *          space environment to use for this instance
     * @param liveActivityRuntimeListener
     *          a live activity runner listener to add to the runner
     */
    public DevelopmentStandaloneLiveActivityRuntime(LiveActivityRuntimeComponentFactory runtimeComponentFactory,
            InteractiveSpacesEnvironment spaceEnvironment,
            LiveActivityRuntimeListener liveActivityRuntimeListener) {
        this.runtimeComponentFactory = runtimeComponentFactory;
        this.spaceEnvironment = spaceEnvironment;
        this.liveActivityRuntimeListener = liveActivityRuntimeListener;

        managedResources = new ManagedResources(spaceEnvironment.getLog());
    }

    @Override
    public void startup() {
        Configuration configuration = spaceEnvironment.getSystemConfiguration();
        addDynamicConfiguration(configuration);

        String instance = configuration.getPropertyString(CONFIGURATION_INTERACTIVESPACES_STANDALONE_INSTANCE, "");
        setInstanceSuffix(instance);

        String activityRuntimePath = configuration
                .getPropertyString(CONFIGURATION_INTERACTIVESPACES_STANDALONE_ACTIVITY_RUNTIME);
        getLog().info("activityRuntimePath is " + activityRuntimePath);
        setActivityRuntimeDir(new File(activityRuntimePath));

        String activitySourcePath = configuration
                .getPropertyString(CONFIGURATION_INTERACTIVESPACES_STANDALONE_ACTIVITY_SOURCE);
        getLog().info("activitySourcePath is " + activitySourcePath);

        String activityConfigPath = configuration
                .getPropertyString(CONFIGURATION_INTERACTIVESPACES_STANDALONE_ACTIVITY_CONFIG);
        getLog().info("activityConfigPath is " + activityConfigPath);

        String standaloneRouterType = configuration
                .getPropertyString(CONFIGURATION_INTERACTIVESPACES_STANDALONE_ROUTER_TYPE);
        if (standaloneRouterType != null) {
            getLog().info("configuring to use router type " + standaloneRouterType);
            setUseStandaloneRouter("standalone".equals(standaloneRouterType));
        }

        setActivityConfigFile(new File(activityConfigPath,
                DevelopmentStandaloneLiveActivityRuntime.ACTIVITY_SPECIFIC_CONFIG_FILE_NAME));
        setLocalConfigFile(
                new File(activityConfigPath, DevelopmentStandaloneLiveActivityRuntime.LOCAL_CONFIG_FILE_NAME));

        List<File> foldersToUse = Lists.newArrayList();
        File rootFolder = new File(activitySourcePath);
        scanForProjects(rootFolder, foldersToUse);
        prepareLiveActivityInformation(rootFolder, foldersToUse);

        SequentialEventQueue eventQueue = new SimpleSequentialEventQueue(spaceEnvironment,
                spaceEnvironment.getLog());
        managedResources.addResource(eventQueue);

        LoggingAlertStatusManager alertStatusManager = new LoggingAlertStatusManager(spaceEnvironment.getLog());
        managedResources.addResource(alertStatusManager);

        StandaloneLiveActivityStorageManager liveActivityStorageManager = new StandaloneLiveActivityStorageManager(
                liveActivityInformation);
        managedResources.addResource(liveActivityStorageManager);

        StandaloneLocalLiveActivityRepository liveActivityRepository = new StandaloneLocalLiveActivityRepository(
                liveActivityInformation, spaceEnvironment);
        managedResources.addResource(liveActivityRepository);

        ActivityInstallationManager activityInstallationManager = new StandaloneActivityInstallationManager();
        managedResources.addResource(activityInstallationManager);

        // TODO(keith): Consider placing in runtime component factory.
        ExpressionEvaluatorFactory expressionEvaluatorFactory = new SimpleExpressionEvaluatorFactory();

        DevelopmentPropertyFileLiveActivityConfigurationManager configurationManager = new DevelopmentPropertyFileLiveActivityConfigurationManager(
                expressionEvaluatorFactory, spaceEnvironment);

        LiveActivityLogFactory activityLogFactory = new InteractiveSpacesEnvironmentLiveActivityLogFactory(
                spaceEnvironment);

        liveActivityRuntime = new StandardLiveActivityRuntime(runtimeComponentFactory, liveActivityRepository,
                activityInstallationManager, activityLogFactory, configurationManager, liveActivityStorageManager,
                alertStatusManager, eventQueue, spaceEnvironment);
        liveActivityRuntime.setLiveActivityStatusPublisher(liveActivityStatusPublisher);
        liveActivityRuntime.addRuntimeListener(liveActivityRuntimeListener);
        managedResources.addResource(liveActivityRuntime);

        managedResources.startupResources();

        playerFuture = spaceEnvironment.getExecutorService().submit(new Runnable() {
            @Override
            public void run() {
                play();
            }
        });
    }

    @Override
    public void shutdown() {
        if (playerFuture != null) {
            playerFuture.cancel(true);
        }

        managedResources.shutdownResourcesAndClear();
    }

    /**
     * Start the run.
     */
    public void play() {
        prepareFilesystem();
        prepareRuntime();
        startupActivity();

        startPlayback();
    }

    /**
     * Scan for project folders.
     *
     * @param folder
     *          the root folder to scan from
     * @param foldersToUse
     *          the list of folders to use
     */
    private void scanForProjects(File folder, List<File> foldersToUse) {
        if (isProjectFolder(folder)) {
            foldersToUse.add(folder);
        } else {
            File[] subfolders = folder.listFiles(DIRECTORY_FILE_FILTER);
            if (subfolders != null) {
                for (File subfolder : subfolders) {
                    scanForProjects(subfolder, foldersToUse);
                }
            }
        }
    }

    /**
     * Is the supplied folder a project folder?
     *
     * @param folder
     *          the folder to test
     *
     * @return {@code true} if the folder is a project folder
     */
    public boolean isProjectFolder(File folder) {
        return fileSupport.exists(fileSupport.newFile(folder, "project.xml"));
    }

    /**
     * Prepare the activity information from the project files.
     *
     * @param rootFolder
     *          the root folder the runner was started in
     * @param foldersToUse
     *          the folders to use for activities
     */
    private void prepareLiveActivityInformation(File rootFolder, List<File> foldersToUse) {
        // No matter what, the runtime folder will be in the project.
        File runtimeFolder = fileSupport.newFile(rootFolder, "run");
        fileSupport.directoryExists(runtimeFolder);

        if (foldersToUse.size() > 1) {
            for (File projectFolder : foldersToUse) {
                String uuid = createActivityUuid(projectFolder);
                File baseActivityRuntimeFolder = fileSupport.newFile(runtimeFolder, uuid);
                compileLiveActivityInformation(uuid, projectFolder, baseActivityRuntimeFolder);
            }
        } else {
            File projectFolder = foldersToUse.get(0);

            compileLiveActivityInformation(createActivityUuid(projectFolder), projectFolder, runtimeFolder);
        }
    }

    /**
     * Create a UUID for a project.
     *
     * @param projectFolder
     *          the project folder
     *
     * @return the UUID
     */
    private String createActivityUuid(File projectFolder) {
        return projectFolder.getName();
    }

    /**
     * Fill in the rest of the information object.
     *
     * @param uuid
     *          the UUID for the activity
     * @param projectFolder
     *          the project folder
     * @param baseActivityRuntimeFolder
     *          the base folder for the activity runtime
     */
    private void compileLiveActivityInformation(String uuid, File projectFolder, File baseActivityRuntimeFolder) {
        StandaloneLiveActivityInformation info = new StandaloneLiveActivityInformation(uuid, projectFolder);

        liveActivityInformation.addInformation(info);

        info.setBaseRuntimeActivityDirectory(baseActivityRuntimeFolder);

        File installDirectory = fileSupport.newFile(info.getBaseSourceDirectory(), ACTIVITY_PATH_BUILD_STAGING);

        File logDirectory = fileSupport.newFile(baseActivityRuntimeFolder,
                SimpleLiveActivityFilesystem.SUBDIRECTORY_LOG);
        File permanentDataDirectory = fileSupport.newFile(baseActivityRuntimeFolder,
                SimpleLiveActivityFilesystem.SUBDIRECTORY_DATA_PERMANENT);
        File tempDataDirectory = fileSupport.newFile(baseActivityRuntimeFolder,
                SimpleLiveActivityFilesystem.SUBDIRECTORY_DATA_TEMPORARY);
        File internalDirectory = fileSupport.newFile(projectFolder, ACTIVITY_PATH_DEVELOPMENT);
        SimpleLiveActivityFilesystem filesystem = new SimpleLiveActivityFilesystem(installDirectory, logDirectory,
                permanentDataDirectory, tempDataDirectory, internalDirectory);
        filesystem.ensureDirectories();

        info.setActivityFilesystem(filesystem);
    }

    /**
     * Get the instance suffix to use for this instance. The suffix returned depends on the number of already running
     * instances.
     *
     * @param rootDir
     *          the root directory that holds the instance locks
     *
     * @return suffix to use for directories and files
     */
    private String findInstanceSuffix(File rootDir) {
        if (instanceSuffix != null) {
            return instanceSuffix;
        }

        int instance = 0;
        while (instance < MAX_INSTANCE_COUNT) {
            String suffix = instance > 0 ? ("-" + instance) : "";
            File lockFile = new File(rootDir, LOCK_FILE_NAME + suffix);
            try {
                RandomAccessFile pidRaf = new RandomAccessFile(lockFile, "rw");
                FileLock fileLock = pidRaf.getChannel().tryLock(0, Long.MAX_VALUE, false);
                if (fileLock != null) {
                    return suffix;
                }
            } catch (IOException e) {
                // Do nothing, increment and try again.
            }
            instance++;
        }
        throw new InteractiveSpacesException("Could not lock run file after " + MAX_INSTANCE_COUNT + " tries");
    }

    /**
     * Get the configuration file to use for local activity configuration.
     *
     * @param configFile
     *          base configuration file
     *
     * @return file to use for local activity configuration
     */
    private File getInstanceConfigFile(File configFile) {
        String rootPath = configFile.getPath();
        int breakIndex = rootPath.lastIndexOf('.');
        String instanceName = rootPath.substring(0, breakIndex) + instanceSuffix + rootPath.substring(breakIndex);
        return new File(instanceName);
    }

    /**
     * Add dynamic configuration parameters to this configuration.
     *
     * @param configuration
     *          the configuration to dynamically update
     */
    private void addDynamicConfiguration(Configuration configuration) {
        try {
            String hostname = InetAddress.getLocalHost().getHostName();
            configuration.setValue(InteractiveSpacesEnvironment.CONFIGURATION_HOSTNAME, hostname);
            configuration.setValue(InteractiveSpacesEnvironment.CONFIGURATION_HOSTID, hostname);
            String hostAddress = InetAddress.getByName(hostname).getHostAddress();
            configuration.setValue(InteractiveSpacesEnvironment.CONFIGURATION_HOST_ADDRESS, hostAddress);
        } catch (UnknownHostException e) {
            throw new InteractiveSpacesException("Could not determine hostname", e);
        }
    }

    /**
     * Prepare the runtime.
     */
    public void prepareRuntime() {
        if (useStandaloneRouter) {
            cecRouter = new StandaloneMessageRouter(this);
            spaceEnvironment.setValue(MessageRouterActivityComponent.class.getName(), cecRouter);
        }
    }

    /**
     * Set the instance suffix for this instance.
     *
     * @param instanceSuffix
     *          instance suffix to use
     */
    public void setInstanceSuffix(String instanceSuffix) {
        this.instanceSuffix = instanceSuffix;
    }

    /**
     * Controls the router to use.
     *
     * @param useStandaloneRouter
     *          {@code true} if the standalone router should be used
     */
    public void setUseStandaloneRouter(boolean useStandaloneRouter) {
        this.useStandaloneRouter = useStandaloneRouter;
    }

    /**
     * Set the activity runtime dir.
     *
     * @param activityRuntimeDir
     *          directory to use for activity runtime files
     */
    public void setActivityRuntimeDir(File activityRuntimeDir) {
        instanceSuffix = findInstanceSuffix(activityRuntimeDir);
        isPrimaryInstance = instanceSuffix.length() == 0;
        File actualRootDir = isPrimaryInstance ? activityRuntimeDir
                : new File(activityRuntimeDir, "instance" + instanceSuffix);
        fileSupport.directoryExists(actualRootDir);
    }

    /**
     * Set the trace send path.
     *
     * @param traceSendPath
     *          trace send path
     */
    public void setTraceSendPath(String traceSendPath) {
        this.traceSendPath = traceSendPath;
    }

    /**
     * Set the local config file.
     *
     * @param localConfigFile
     *          local config file
     */
    public void setLocalConfigFile(File localConfigFile) {
        this.localConfigFile = getInstanceConfigFile(localConfigFile);
    }

    /**
     * Set the activity config file.
     *
     * @param activityConfigFile
     *          activity config file
     */
    public void setActivityConfigFile(File activityConfigFile) {
        this.activityConfigFile = getInstanceConfigFile(activityConfigFile);
    }

    /**
     * Set the trace check path.
     *
     * @param traceCheckPath
     *          trace check path
     */
    public void setTraceCheckPath(String traceCheckPath) {
        this.traceCheckPath = traceCheckPath;
    }

    /**
     * Set the trace playback path.
     *
     * @param tracePlaybackPath
     *          trace playback path
     */
    public void setTracePlaybackPath(String tracePlaybackPath) {
        this.tracePlaybackPath = tracePlaybackPath;
    }

    /**
     * Set the trace filter path.
     *
     * @param traceFilterPath
     *          trace filter path
     */
    public void setTraceFilterPath(String traceFilterPath) {
        this.traceFilterPath = traceFilterPath;
    }

    /**
     * Prepare the filesystem for use. Makes sure necessary directories exist.
     */
    public void prepareFilesystem() {
        if (!isPrimaryInstance) {
            // When running multiple instances, require explicit local config files to
            // make it more obvious what they should be.
            if (!activityConfigFile.exists()) {
                throw new InteractiveSpacesException(
                        "Missing activity config file " + activityConfigFile.getAbsolutePath());
            }
        }

        spaceEnvironment.getLog().info("Using activity config file " + activityConfigFile.getAbsolutePath());
    }

    /**
     * Start any trace playback.
     */
    public void startPlayback() {
        if (tracePlaybackPath != null) {
            cecRouter.playback(tracePlaybackPath, false);
        }

        if (traceSendPath != null) {
            cecRouter.playback(traceSendPath, true);
        }
    }

    /**
     * Startup the activity.
     */
    public void startupActivity() {
        if (traceCheckPath != null) {
            cecRouter.checkStart(traceCheckPath);
        }

        if (traceFilterPath != null) {
            cecRouter.setTraceFilter(traceFilterPath);
        }

        for (InstalledLiveActivity activity : liveActivityRuntime.getAllInstalledLiveActivities()) {
            liveActivityRuntime.activateLiveActivity(activity.getUuid());
        }
    }

    /**
     * An activity status update has happened.
     *
     * @param uuid
     *          uuid of activity
     * @param status
     *          status of the activity
     */
    private void onPublishActivityStatus(String uuid, ActivityStatus status) {
        // TODO(keith): If any signal crash, should shut runner down.

        spaceEnvironment.getLog().info(String.format("Activity status for activity %s is now %s", uuid, status));
    }

    /**
     * Signal completion, either due to an error or successful verification of all messages.
     *
     * @param success
     *          {@code true} if completion is due to successful message verification
     */
    public void signalCompletion(boolean success) {
        // Need to do this in another thread because we don't know the context the error is occurring in.
        // Specifically, the web-server gets unhappy if you try to exit from an io-worker thread.
        new Thread(new ExitHelper(success)).start();
    }

    /**
     * Handle an error by the activity.
     *
     * @param msg
     *          message to include with the error
     * @param throwable
     *          exception that caused the error
     */
    public synchronized void handleError(String msg, Throwable throwable) {
        getLog().error(msg, throwable);
    }

    /**
     * Helper runner class for making a clean exit.
     */
    class ExitHelper implements Runnable {
        /**
         * Save the success code, we need it when exiting.
         */
        private boolean success;

        /**
         * Exit helper.
         *
         * @param success
         *          success state of the system
         */
        public ExitHelper(boolean success) {
            this.success = success;
        }

        /**
         * Actually shut down the system.
         */
        @Override
        public void run() {
            try {
                liveActivityRuntime.shutdownAllActivities();
            } catch (Exception e) {
                getLog().error("Error encountered during shutdown", e);
                success = false;
            }

            int returnCode = success ? 0 : 1;
            getLog().error("Exiting with result code " + returnCode);
            System.exit(returnCode);
        }
    }

    /**
     * @return logger to use
     */
    private Log getLog() {
        return spaceEnvironment.getLog();
    }

    /**
     * Get the live activity runtime for the standalone runner.
     *
     * @return the live activity runtime
     */
    public StandardLiveActivityRuntime getLiveActivityRuntime() {
        return liveActivityRuntime;
    }
}