net.scriptability.core.ScriptAbility.java Source code

Java tutorial

Introduction

Here is the source code for net.scriptability.core.ScriptAbility.java

Source

/*
 * Copyright 2013 Hayden Bakkum
 *
 *  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 net.scriptability.core;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import net.scriptability.core.concurrent.AsyncEventListenerThreadPool;
import net.scriptability.core.configuration.Configuration;
import net.scriptability.core.configuration.ConfigurationException;
import net.scriptability.core.configuration.loader.ConfigurationLoader;
import net.scriptability.core.event.Event;
import net.scriptability.core.scheduler.EventScheduler;
import net.scriptability.core.util.ResourceLoader;
import net.scriptability.core.visitor.EventSchedulingVisitor;
import net.scriptability.core.visitor.ScriptCompilingVisitor;
import net.scriptability.core.visitor.Visitor;
import net.scriptability.core.visitor.VisitorException;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.*;

import static net.scriptability.core.util.Preconditions.checkIsNotNullOrEmpty;
import static net.scriptability.core.util.Preconditions.checkNotNull;

/**
 * Core class of the framework that all clients will interact with.
 * 
 * When loaded, this class will attempt to read in properties from scriptability.properties if it can be found on the classpath.
 * This property file is optional however and default values will be used for the properties the framework requires if it is not found.
 * 
 * Each client can obtain a reference to the singleton instance of this class by statically invoking {@link #getScriptAbility()}.
 * 
 * To do any meaningful work the framework needs to be initialised by first invoking one the start() methods. Attempts to fire
 * events before calling start() will result in the event being effectively ignored as any calls to fire() will return immediately without side effect.
 * Calling start() will first initialise a set of services required by the framework after which the frameworks user defined configuration will be
 * loaded from some data source via a {@link ConfigurationLoader}. In most cases the start() method should only be called once in the lifetime of a
 * client application process.
 * 
 * In order to reload the frameworks configuration using the ConfigurationLoader, the {@link #refresh()} method can be invoked.
 * 
 * To cleanly shutdown the framework, the {@link #stop()} method should be called.
 * 
 * @author Hayden Bakkum
 * 
 */
public final class ScriptAbility {

    /**
     * SLF4J logger.
     */
    private static final Logger LOG = LoggerFactory.getLogger(ScriptAbility.class);

    /**
     * File name of frameworks property file. An attempt will be made to load this from the classpath when this class is loaded.
     */
    private static final String SCRIPT_ABILITY_PROPERTY_FILE_NAME = "scriptability.properties";

    /**
     * Framework properties.
     */
    private static Properties scriptAbilityProperties = new Properties();

    /**
     * If found, load properties from the frameworks property file.
     */
    static {
        final URL url = ResourceLoader.getResource(SCRIPT_ABILITY_PROPERTY_FILE_NAME);
        if (url != null) {
            LOG.info("Found scriptability properties file here: [" + url + "].");

            InputStream inputStream = null;
            try {
                inputStream = url.openStream();
                scriptAbilityProperties.load(inputStream);

            } catch (final IOException ioEx) {
                throw new ScriptAbilityException(
                        "Error loading properties file: [" + SCRIPT_ABILITY_PROPERTY_FILE_NAME + "].", ioEx);
            } finally {
                IOUtils.closeQuietly(inputStream);
            }
        } else {
            LOG.warn(
                    "Could not find scriptability property file on the classpath. Framework default properties will be used.");
        }
    }

    /**
     * Singleton instance of this class.
     */
    private static final ScriptAbility INSTANCE = new ScriptAbility();

    /**
     * Factory used to create services required by the framework.
     */
    private ScriptAbilityFactory factory;

    /**
     * Loads framework configuration from some data source.
     */
    private ConfigurationLoader configurationLoader;

    /**
     * Bindings that will be provided to scripts.
     */
    private Map<String, Object> scriptBindings;

    /**
     * Schedules firing of events.
     */
    private EventScheduler eventScheduler;

    /**
     * The currently loaded configuration.
     */
    private Configuration configuration;

    /**
     * Returns the singleton instance of this class.
     * 
     * @return singleton instance of ScriptAbility.
     */
    public static ScriptAbility getScriptAbility() {
        return INSTANCE;
    }

    /**
     * Fires the named event which will be handled by any listening scripts. If the framework has not yet been started the event will be ignored.
     * 
     * @param eventName name of the event to fire.
     */
    public void fireEvent(final String eventName) {
        final Map<String, String> emptyEventContext = Maps.newHashMap();

        fireEvent(eventName, emptyEventContext);
    }

    /**
     * Fires the named event which will be handled by any listening scripts. The associated contextual data will also be provided to the listening
     * scripts. If the framework has not yet been started the event will be ignored.
     * 
     * @param eventName name of the event to fire.
     * @param eventContext contextual data associated with the fired event.
     */
    public void fireEvent(final String eventName, final Map<String, String> eventContext) {
        checkIsNotNullOrEmpty(eventName, "eventName");
        checkNotNull(eventContext, "eventContext");

        if (!isStarted()) {
            LOG.warn("Cannot fire event [{}] as ScriptAbility has not been started yet.", eventName);
            return;
        }

        Event event = configuration.getEvent(eventName);
        if (event != null) {
            LOG.debug("Firing event: [{}]", eventName);
            event.fire(eventContext);
        } else {
            LOG.warn("Unknown event: [{}]", eventName);
        }
    }

    /**
     * Starts the framework without any script bindings.
     *
     */
    public synchronized void start() {
        start(new HashMap<String, Object>());
    }

    /**
     * Starts the framework using the supplied map as the script API. API objects will be bound as script variables using the map
     * key for the variable name.
     * 
     * @param scriptBindings bindings provided to scripts.
     */
    public synchronized void start(final Map<String, Object> scriptBindings) {
        start(scriptBindings, new ScriptAbilityFactory());
    }

    /**
     * Starts the framework using the supplied map as the script API. API objects will be bound as script variables using the map
     * key for the variable name.
     *
     * @param factory factory used for creating various framework services.
     */
    public synchronized void start(final ScriptAbilityFactory factory) {
        start(Collections.EMPTY_MAP, new ScriptAbilityFactory());
    }

    /**
     * Starts the framework using the supplied map as the script API. API objects will be bound as script variables using the map
     * key for the variable name.
     * 
     * @param scriptBindings bindings provided to scripts.
     * @param factory factory used for creating various framework services.
     */
    public synchronized void start(final Map<String, Object> scriptBindings, final ScriptAbilityFactory factory) {
        if (isStarted()) {
            LOG.warn("ScriptAbility has already been started. This invocation of start() will have no effect.");
            return;
        }

        LOG.info("Starting ScriptAbility...");

        this.scriptBindings = Collections.unmodifiableMap(scriptBindings);
        this.factory = factory;
        this.configurationLoader = factory.createConfigurationLoader();

        AsyncEventListenerThreadPool.start(scriptAbilityProperties);

        try {
            refreshConfiguration();
        } catch (ConfigurationException | VisitorException ex) {
            throw new ScriptAbilityException("Error starting ScriptAbility.", ex);
        }

        LOG.info("ScriptAbility started successfully.");
    }

    /**
     * Refreshes configuration.
     */
    public synchronized void refresh() {
        if (!isStarted()) {
            LOG.warn("ScriptAbility has not been started. This invocation of refresh() will have no effect.");
            return;
        }

        try {
            refreshConfiguration();
        } catch (ConfigurationException | VisitorException ex) {
            LOG.error("Error refreshing ScriptAbility configuration.", ex);
        }
    }

    /**
     * Stop scriptability.
     */
    public synchronized void stop() {
        if (!isStarted()) {
            LOG.warn("ScriptAbility has not been started. This invocation of shutdown() will have no effect.");
            return;
        }

        eventScheduler.stop();
        configuration = null;

        AsyncEventListenerThreadPool.shutdown();

        LOG.info("Successfully stopped ScriptAbility.");
    }

    /**
     * Indicates whether or not the framework has been started.
     *
     * @return true if the framework has started, false otherwise.
     */
    public boolean isStarted() {
        return configuration != null;
    }

    /**
     * Returns the properties loaded from the frameworks property file.
     * 
     * @return properties loaded from scriptability.properties.
     */
    static Properties getScriptAbilityProperties() {
        return scriptAbilityProperties;
    }

    private ScriptAbility() {
    }

    private synchronized void refreshConfiguration() throws VisitorException, ConfigurationException {
        Configuration configuration = loadConfiguration();

        EventScheduler eventScheduler = factory.createEventScheduler();
        List<Visitor> configurationVisitors = getConfigurationVisitors(eventScheduler);
        for (Visitor configurationVisitor : configurationVisitors) {
            configuration.visit(configurationVisitor);
        }

        if (this.eventScheduler != null) {
            this.eventScheduler.stop();
        }
        this.configuration = configuration;
        this.eventScheduler = eventScheduler;
        this.eventScheduler.start();
    }

    private Configuration loadConfiguration() throws ConfigurationException {
        final Configuration configuration = factory.createConfiguration();
        configurationLoader.loadConfiguration(factory.createConfigurator(configuration));

        return configuration;
    }

    private List<Visitor> getConfigurationVisitors(final EventScheduler eventScheduler) {
        List<Visitor> visitors = Lists.newArrayList();
        visitors.add(new EventSchedulingVisitor(eventScheduler));
        visitors.add(new ScriptCompilingVisitor(scriptBindings));

        return visitors;
    }

}