Java tutorial
/* * This file is part of the openSCADA project * * Copyright (C) 2006-2012 TH4 SYSTEMS GmbH (http://th4-systems.com) * Copyright (C) 2013 Jens Reimann (ctron@dentrassi.de) * * openSCADA is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 * only, as published by the Free Software Foundation. * * openSCADA is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License version 3 for more details * (a copy is included in the LICENSE file that accompanied this code). * * You should have received a copy of the GNU Lesser General Public License * version 3 along with openSCADA. If not, see * <http://opensource.org/licenses/lgpl-3.0.html> for a copy of the LGPLv3 License. */ package org.openscada.ae.monitor.script; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Executor; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; import javax.script.SimpleScriptContext; import org.openscada.ae.data.MonitorStatusInformation; import org.openscada.ae.data.Severity; import org.openscada.ae.event.EventProcessor; import org.openscada.ae.monitor.common.AbstractPersistentStateMonitor; import org.openscada.ae.monitor.common.DemoteImpl; import org.openscada.ae.monitor.common.PersistentInformation; import org.openscada.ae.monitor.datasource.MonitorStateInjector; import org.openscada.ae.monitor.script.ScriptMonitorResult.FailureBuilder; import org.openscada.ae.monitor.script.ScriptMonitorResult.OkBuilder; import org.openscada.ca.ConfigurationAdministrator; import org.openscada.ca.ConfigurationDataHelper; import org.openscada.core.Variant; import org.openscada.core.server.OperationParameters; import org.openscada.da.client.DataItemValue; import org.openscada.da.core.WriteAttributeResults; import org.openscada.da.datasource.DataSource; import org.openscada.da.datasource.DataSourceHandler; import org.openscada.da.datasource.MultiDataSourceListener; import org.openscada.da.master.MasterItem; import org.openscada.sec.UserInformation; import org.openscada.utils.osgi.pool.ObjectPoolTracker; import org.openscada.utils.script.ScriptExecutor; import org.osgi.framework.BundleContext; import org.osgi.util.tracker.ServiceTracker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.Interner; public class ScriptMonitor extends AbstractPersistentStateMonitor { private final static Logger logger = LoggerFactory.getLogger(ScriptMonitor.class); public class InjectMasterHandler extends MultiMasterHandler { public InjectMasterHandler(final String configurationId, final ObjectPoolTracker<MasterItem> poolTracker, final int priority, final ServiceTracker<ConfigurationAdministrator, ConfigurationAdministrator> caTracker, final String prefix, final String factoryId) { super(configurationId, poolTracker, priority, caTracker, prefix, factoryId); } @Override public void dataUpdate(final Map<String, Object> context, final DataItemValue.Builder value) { ScriptMonitor.this.dataUpdate(context, value); } @Override protected WriteAttributeResults handleUpdate(final Map<String, Variant> attributes, final OperationParameters operationParameters) throws Exception { return null; } @Override protected void reprocess() { super.reprocess(); } } private static final String DEFAULT_ENGINE_NAME = "JavaScript"; //$NON-NLS-1$ private final InjectMasterHandler handler; private final DemoteImpl demoteImpl = new DemoteImpl(); private final MultiDataSourceListener listener; private final MonitorStateInjector monitorStateInjector; private final ClassLoader classLoader; private final ScriptEngineManager manager; private SimpleScriptContext scriptContext; private ScriptEngine scriptEngine; private ScriptExecutor updateCommand; private final ScriptMonitorResult lastResult = new ScriptMonitorResult(); private final String prefix; private final Executor executor; public ScriptMonitor(final String id, final String factoryId, final Executor executor, final BundleContext context, final Interner<String> stringInterner, final EventProcessor eventProcessor, final ObjectPoolTracker<DataSource> dataSourcePoolTracker, final ObjectPoolTracker<MasterItem> masterItemPoolTracker, final ServiceTracker<ConfigurationAdministrator, ConfigurationAdministrator> caTracker) { super(id, factoryId, executor, context, stringInterner, eventProcessor); this.executor = executor; this.prefix = stringInterner.intern(factoryId + ". " + id); //$NON-NLS-1$ this.classLoader = getClass().getClassLoader(); this.monitorStateInjector = new MonitorStateInjector(stringInterner); this.monitorStateInjector.setPrefix(this.prefix); final ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(this.classLoader); this.manager = new ScriptEngineManager(this.classLoader); } finally { Thread.currentThread().setContextClassLoader(currentClassLoader); } this.handler = new InjectMasterHandler(id, masterItemPoolTracker, 0, caTracker, this.prefix, factoryId); this.listener = new MultiDataSourceListener(dataSourcePoolTracker) { @Override protected void handleChange(final Map<String, DataSourceHandler> sources) { ScriptMonitor.this.handleChange(sources); } }; } @Override public void dispose() { this.handler.dispose(); this.listener.dispose(); super.dispose(); } @Override protected synchronized void notifyStateChange(final MonitorStatusInformation state) { super.notifyStateChange(state); this.monitorStateInjector.notifyStateChange(state); this.handler.reprocess(); } @Override protected synchronized void applyPersistentInformation(final PersistentInformation persistentInformation) { super.applyPersistentInformation(persistentInformation); // information was updated .. now we need to update the DA attributes handleChange(this.listener.getSourcesCopy()); } @Override public void update(final UserInformation userInformation, final Map<String, String> properties) throws Exception { logger.info("Changing configuration - {}", properties); //$NON-NLS-1$ final ConfigurationDataHelper cfg = new ConfigurationDataHelper(properties); setScript(cfg); this.demoteImpl.update(userInformation, properties); this.handler.update(userInformation, properties); this.listener.setDataSources(properties); setSuppressEvents(cfg.getBoolean("suppressEvents", false)); //$NON-NLS-1$ setStringAttributes(cfg.getPrefixed("info.")); //$NON-NLS-1$ handleChange(this.listener.getSourcesCopy()); } private synchronized void setScript(final ConfigurationDataHelper cfg) throws ScriptException, IOException { String engine = cfg.getString("scriptEngine", DEFAULT_ENGINE_NAME); //$NON-NLS-1$ if ("".equals(engine)) // catches null { engine = DEFAULT_ENGINE_NAME; } this.scriptContext = new SimpleScriptContext(); this.scriptEngine = this.manager.getEngineByName(engine); if (this.scriptEngine == null) { throw new IllegalArgumentException(String.format("'%s' is not a valid script engine", engine)); } // trigger init script final String initScript = cfg.getString("init"); //$NON-NLS-1$ if (initScript != null) { new ScriptExecutor(this.scriptEngine, initScript, this.classLoader).execute(this.scriptContext); } this.updateCommand = makeScript(cfg.getString("updateCommand")); //$NON-NLS-1$ } private ScriptExecutor makeScript(final String string) throws ScriptException { if (string == null || string.isEmpty()) { return null; } return new ScriptExecutor(this.scriptEngine, string, this.classLoader); } public synchronized DataItemValue dataUpdate(final Map<String, Object> context, final DataItemValue.Builder builder) { this.demoteImpl.handleDataUpdate(context, builder); this.monitorStateInjector.injectAttributes(builder); return builder.build(); } protected void handleChange(final Map<String, DataSourceHandler> sources) { final Map<String, DataItemValue> values = new HashMap<String, DataItemValue>(sources.size()); for (final Map.Entry<String, DataSourceHandler> entry : sources.entrySet()) { values.put(entry.getKey(), entry.getValue().getValue()); } this.executor.execute(new Runnable() { @Override public void run() { evaluate(values); } }); } protected synchronized void evaluate(final Map<String, DataItemValue> values) { applyState(evaluateState(values)); } private synchronized void applyState(final ScriptMonitorResult evaluateState) { logger.debug("Apply state: {}", evaluateState); //$NON-NLS-1$ switch (evaluateState.monitorStatus) { case UNSAFE: setUnsafe(); break; case OK: setOk(evaluateState.value, evaluateState.valueTimestamp); break; case FAILURE: setFailure(evaluateState.value, evaluateState.valueTimestamp, evaluateState.severity, evaluateState.requireAck == null ? true : evaluateState.requireAck); break; case INACTIVE: setInactive(); break; } } private ScriptMonitorResult evaluateState(final Map<String, DataItemValue> values) { final Map<String, Object> scriptObjects = new HashMap<String, Object>(); scriptObjects.put("values", values); //$NON-NLS-1$ scriptObjects.put("UNSAFE", ScriptMonitorResult.UNSAFE); //$NON-NLS-1$ scriptObjects.put("INACTIVE", ScriptMonitorResult.INACTIVE); //$NON-NLS-1$ scriptObjects.put("OK", OkBuilder.INSTANCE); //$NON-NLS-1$ scriptObjects.put("FAILURE", FailureBuilder.INSTANCE); //$NON-NLS-1$ scriptObjects.put("result", this.lastResult); //$NON-NLS-1$ try { logger.debug("Running update command - values: {}", values); //$NON-NLS-1$ return convertState(this.updateCommand.execute(this.scriptContext, scriptObjects)); } catch (final Exception e) { logger.warn("Failed to evaluate monitor", e); //$NON-NLS-1$ return ScriptMonitorResult.UNSAFE; } } private ScriptMonitorResult convertState(final Object execute) { logger.debug("Converting: {}", execute); //$NON-NLS-1$ if (execute == null) { return ScriptMonitorResult.UNSAFE; } if (execute instanceof ScriptMonitorResult) { return (ScriptMonitorResult) execute; } return ScriptMonitorResult.UNSAFE; } @Override protected void setFailure(final Variant value, final Long valueTimestamp, final Severity severity, final boolean requireAck) { final Severity result = this.demoteImpl.demoteSeverity(severity); if (result == null) { setOk(value, valueTimestamp); } else { super.setFailure(value, valueTimestamp, result, this.demoteImpl.demoteAck(requireAck)); } } }