Java tutorial
/* * 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; } }