org.openscada.vi.ui.draw2d.SymbolController.java Source code

Java tutorial

Introduction

Here is the source code for org.openscada.vi.ui.draw2d.SymbolController.java

Source

/*
 * 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.vi.ui.draw2d;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.console.ConsolePlugin;
import org.eclipse.ui.console.IConsole;
import org.eclipse.ui.console.IConsoleManager;
import org.eclipse.ui.console.MessageConsole;
import org.eclipse.ui.console.MessageConsoleStream;
import org.eclipse.ui.statushandlers.StatusManager;
import org.openscada.core.Variant;
import org.openscada.core.ui.styles.StyleGenerator;
import org.openscada.core.ui.styles.StyleGenerator.GeneratorListener;
import org.openscada.sec.ui.DisplayCallbackHandler;
import org.openscada.ui.utils.status.StatusHelper;
import org.openscada.utils.script.ScriptExecutor;
import org.openscada.vi.data.DataValue;
import org.openscada.vi.data.RegistrationManager;
import org.openscada.vi.data.RegistrationManager.Listener;
import org.openscada.vi.data.SummaryInformation;
import org.openscada.vi.data.SummaryListener;
import org.openscada.vi.model.VisualInterface.Primitive;
import org.openscada.vi.model.VisualInterface.Symbol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.io.Resources;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class SymbolController implements Listener {

    private final static Logger logger = LoggerFactory.getLogger(SymbolController.class);

    private final SymbolController parentController;

    private final Set<SymbolController> controllers = new LinkedHashSet<SymbolController>();

    private final ScriptExecutor onInit;

    private final ScriptEngineManager engineManager;

    private final ScriptEngine engine;

    private final ScriptExecutor onDispose;

    private final ScriptExecutor onUpdate;

    private final Properties properties;

    private final SymbolContext context;

    private final ScriptContext scriptContext;

    private final ClassLoader classLoader;

    private final Map<String, Object> elements = new HashMap<String, Object>();

    private final Map<Primitive, Object> primitives = new HashMap<Primitive, Object>();

    private RegistrationManager registrationManager;

    private final SymbolData symbolData;

    private Map<String, DataValue> lastData;

    private final Set<SummaryListener> summaryListeners = new LinkedHashSet<SummaryListener>(1);

    private final Map<String, Object> scriptObjects;

    private MessageConsole console;

    private MessageConsole createdConsole;

    private MessageConsoleStream logStream;

    private MessageConsoleStream errorStream;

    private final String symbolInfoName;

    private final List<String> nameHierarchy;

    private final GeneratorListener generatorListener = new GeneratorListener() {

        @Override
        public void configurationChanged() {
            generatorConfigurationChanged();
        }
    };

    private final StyleGenerator generator;

    private final Shell shell;

    public SymbolController(final Shell shell, final String symbolInfoName, final Symbol symbol,
            final ClassLoader classLoader, final Map<String, String> properties,
            final Map<String, Object> scriptObjects) throws Exception {
        this(shell, symbolInfoName, null, symbol, classLoader, properties, scriptObjects);
    }

    public SymbolController(final Shell shell, final String symbolInfoName, final SymbolController parentController,
            final Symbol symbol, final ClassLoader classLoader, final Map<String, String> properties,
            final Map<String, Object> scriptObjects) throws Exception {
        this.shell = shell;
        this.symbolInfoName = symbolInfoName;
        this.parentController = parentController;
        this.classLoader = classLoader;

        this.generator = org.openscada.core.ui.styles.Activator.getDefaultStyleGenerator();

        this.nameHierarchy = makeNameHierarchy();

        this.symbolData = new SymbolData(this);
        this.registrationManager = new RegistrationManager(Activator.getDefault().getBundle().getBundleContext(),
                symbolInfoName);
        this.registrationManager.addListener(this);
        this.registrationManager.open();
        this.engineManager = new ScriptEngineManager(classLoader);

        if (parentController != null) {
            this.properties = new Properties(parentController.getProperties());
        } else {
            this.properties = new Properties();
        }

        for (final Map.Entry<String, String> entry : symbol.getProperties().entrySet()) {
            if (entry.getValue() != null) {
                this.properties.put(entry.getKey(), entry.getValue());
            }
        }

        for (final Map.Entry<String, String> entry : properties.entrySet()) {
            if (entry.getValue() != null) {
                this.properties.put(entry.getKey(), entry.getValue());
            }
        }

        this.engine = this.engineManager.getEngineByName("JavaScript");
        this.context = new SymbolContext(this);

        if (parentController != null) {
            parentController.addChild(this);
        }

        this.scriptContext = this.engine.getContext();
        assignConsole(this.scriptContext);

        this.scriptContext.setAttribute("controller", this.context, ScriptContext.ENGINE_SCOPE);
        this.scriptContext.setAttribute("data", this.symbolData, ScriptContext.ENGINE_SCOPE);
        this.scriptContext.setAttribute("GSON", createJson(), ScriptContext.ENGINE_SCOPE);
        this.scriptContext.setAttribute("styleGenerator", this.generator, ScriptContext.ENGINE_SCOPE);
        this.scriptObjects = scriptObjects;
        addScriptObjects(scriptObjects);
        if (parentController != null) {
            addScriptObjects(parentController.getScriptObjects());
        }

        for (final String module : symbol.getScriptModules()) {
            loadScript(module);
        }

        this.onInit = new ScriptExecutor(this.engine, symbol.getOnInit(), classLoader);
        this.onDispose = new ScriptExecutor(this.engine, symbol.getOnDispose(), classLoader);
        this.onUpdate = new ScriptExecutor(this.engine, symbol.getOnUpdate(), classLoader);

        this.generator.addListener(this.generatorListener);
    }

    /**
     * @since 1.1
     */
    public Shell getShell() {
        return this.shell;
    }

    protected void generatorConfigurationChanged() {
        runUpdate(true);
    }

    private List<String> makeNameHierarchy() {
        final List<String> result = new LinkedList<String>();
        SymbolController current = this;
        while (current != null) {
            result.add(0, current.symbolInfoName);
            current = current.parentController;
        }
        return Collections.unmodifiableList(result);
    }

    private void assignConsole(final ScriptContext scriptContext) {
        this.console = createOrGetConsole();

        // scriptContext.setReader ( new InputStreamReader ( this.console.getInputStream () ) );

        /* wrapping into a PrintWriter is necessary due to
         * http://bugs.sun.com/view_bug.do?bug_id=6759414
         * */

        final MessageConsoleStream writerStream = this.console.newMessageStream();
        scriptContext.setWriter(new PrintWriter(new OutputStreamWriter(writerStream)));

        this.errorStream = this.console.newMessageStream();
        this.errorStream.setColor(Display.getDefault().getSystemColor(SWT.COLOR_RED));
        scriptContext.setErrorWriter(new PrintWriter(new OutputStreamWriter(this.errorStream)));

        this.logStream = this.console.newMessageStream();
        this.logStream.setColor(Display.getDefault().getSystemColor(SWT.COLOR_GRAY));
    }

    private MessageConsole createOrGetConsole() {
        if (this.parentController != null && this.parentController.getConsole() != null) {
            return this.parentController.getConsole();
        }

        final IConsoleManager manager = ConsolePlugin.getDefault().getConsoleManager();
        final MessageConsole console = new MessageConsole(
                String.format("Symbol Debug Console: %s", this.symbolInfoName), null, null, true);
        manager.addConsoles(new IConsole[] { console });
        this.createdConsole = console;
        return console;
    }

    protected MessageConsole getConsole() {
        if (this.console != null) {
            return this.console;
        } else {
            return null;
        }
    }

    private Gson createJson() {
        return new GsonBuilder().serializeNulls().setDateFormat("yyyy-MM-dd hh:mm:ss.SSS").create();
    }

    private void addScriptObjects(final Map<String, Object> scriptObjects) {
        if (scriptObjects != null) {
            for (final Map.Entry<String, Object> entry : scriptObjects.entrySet()) {
                this.scriptContext.setAttribute(entry.getKey(), entry.getValue(), ScriptContext.ENGINE_SCOPE);
            }
        }
    }

    public Map<String, Object> getScriptObjects() {
        return this.scriptObjects;
    }

    private void loadScript(final String module) throws Exception {
        this.logStream.println(String.format("Loading script module: %s", module));
        final String moduleSource = Resources.toString(new URL(module), Charset.forName("UTF-8"));

        new ScriptExecutor(this.engine, moduleSource, this.classLoader).execute(this.scriptContext);
    }

    public void init() throws Exception {
        this.onInit.execute(this.scriptContext);
        for (final SymbolController controller : this.controllers) {
            controller.init();
        }
    }

    public Properties getProperties() {
        return this.properties;
    }

    protected void addChild(final SymbolController controller) {
        this.controllers.add(controller);
    }

    protected void removeChild(final SymbolController controller) {
        this.controllers.remove(controller);
    }

    public void dispose() {
        // run onDispose ... when requested
        try {
            if (this.onDispose != null) {
                this.onDispose.execute(this.scriptContext);
            }
        } catch (final Exception e) {
            logger.warn("Failed to dispose", e);
        }

        // unregister from generator
        this.generator.removeListener(this.generatorListener);
        this.generator.dispose();

        // close console

        if (this.createdConsole != null) {
            ConsolePlugin.getDefault().getConsoleManager().removeConsoles(new IConsole[] { this.createdConsole });
            this.createdConsole = null;
        }

        // close log stream

        if (this.logStream != null && !this.logStream.isClosed()) {
            try {
                this.logStream.close();
            } catch (final IOException e) {
                logger.warn("Failed to close log stream", e);
            } finally {
                this.logStream = null;
            }
        }

        if (this.parentController != null) {
            this.parentController.removeChild(this);
        }

        // dispose childs
        final ArrayList<SymbolController> controllers = new ArrayList<SymbolController>(this.controllers);
        for (final SymbolController controller : controllers) {
            controller.dispose();
        }
        this.controllers.clear();

        // dispose registration manager

        if (this.registrationManager != null) {
            this.registrationManager.dispose();
            this.registrationManager = null;
        }
    }

    public Object createProperties(final String command, final String onCreateProperties,
            final Map<String, String> currentProperties) throws Exception {
        final ScriptExecutor executor = new ScriptExecutor(this.engine, onCreateProperties, this.classLoader);
        final Map<String, Object> localProperties = new HashMap<String, Object>(1);
        localProperties.put("properties", currentProperties);
        return executor.execute(this.scriptContext, localProperties);
    }

    public Object getElement(final String name) {
        return this.elements.get(name);
    }

    public Object getElement(final Primitive primitive) {
        return this.primitives.get(primitive);
    }

    public void addRawElement(final String name, final Object element) {
        if (name == null) {
            return;
        }
        this.elements.put(name, element);
    }

    public void addElement(final Primitive primitive, final Object element) {
        if (primitive == null) {
            return;
        }

        if (primitive.getName() != null) {
            this.elements.put(primitive.getName(), element);
        }
        this.primitives.put(primitive, element);
    }

    public void removeElement(final Primitive primitive) {
        if (primitive == null) {
            return;
        }
        this.primitives.remove(primitive);
        this.elements.remove(primitive.getName());
    }

    public void unregisterItem(final String name) {
        this.registrationManager.unregisterItem(name);
    }

    public void registerItem(final String name, final String itemId, final String connectionId,
            final boolean ignoreSummary, final boolean nullInvalid) {
        this.registrationManager.registerItem(name, itemId, connectionId, ignoreSummary, nullInvalid);
    }

    /**
     * Trigger the controller to update the data from the registration manager
     * <p>
     * This method can be called from any thread and must synchronized to the UI
     * </p>
     */
    @Override
    public void triggerDataUpdate() {
        try {
            Display.getDefault().asyncExec(new Runnable() {
                @Override
                public void run() {
                    handleDataUpdate();
                };
            });
        } catch (final Exception e) {
            StatusManager.getManager().handle(StatusHelper.convertStatus(Activator.PLUGIN_ID, e));
        }
    }

    public Map<String, DataValue> getRegistrationManagerData() {
        return this.registrationManager.getData();
    }

    public SummaryInformation getSummaryInformation() {
        return new SummaryInformation(this.nameHierarchy, this.registrationManager.getData(),
                collectChildrenData());
    }

    private Collection<SummaryInformation> collectChildrenData() {
        final Collection<SummaryInformation> result = new LinkedList<SummaryInformation>();

        for (final SymbolController controller : this.controllers) {
            result.add(controller.getSummaryInformation());
        }

        return result;
    }

    protected void handleDataUpdate() {
        if (this.registrationManager == null) {
            // dispose?
            return;
        }

        final Map<String, DataValue> currentData = this.registrationManager.getData();
        if (currentData == this.lastData) {
            return;
        }
        this.lastData = currentData;

        runUpdate(false);
    }

    private void runUpdate(final boolean ignoreParents) {
        logger.debug("Running update: {}", this.nameHierarchy);
        try {
            this.onUpdate.execute(this.scriptContext);
        } catch (final Exception e) {
            StatusManager.getManager().handle(StatusHelper.convertStatus(Activator.PLUGIN_ID, e),
                    StatusManager.LOG);
        }
        notifySummaryListeners();

        // propagate update
        if (!ignoreParents && this.parentController != null) {
            this.parentController.runUpdate(false);
        }
    }

    protected void notifySummaryListeners() {
        final SummaryInformation info = getSummaryInformation();

        logger.debug("notify summary: {}", info);

        for (final SummaryListener listener : this.summaryListeners) {
            logger.debug("notify to: {}", listener);
            listener.summaryChanged(info);
        }
    }

    public void addSummaryListener(final SummaryListener listener) {
        if (this.summaryListeners.add(listener)) {
            listener.summaryChanged(getSummaryInformation());
        }
    }

    public void removeSummaryListener(final SummaryListener listener) {
        this.summaryListeners.remove(listener);
    }

    public ScriptExecutor createScriptExecutor(final String command) throws ScriptException {
        if (command == null || command.isEmpty()) {
            return null;
        }

        return new ScriptExecutor(this.engine, command, this.classLoader);
    }

    public void execute(final ScriptExecutor scriptExecutor, final Map<String, Object> scriptObjects) {
        if (scriptExecutor == null) {
            return;
        }

        try {
            scriptExecutor.execute(this.scriptContext, scriptObjects);
        } catch (final Exception e) {
            StatusManager.getManager().handle(StatusHelper.convertStatus(Activator.PLUGIN_ID, e),
                    StatusManager.LOG);
        }
    }

    public void startWrite(final String connectionId, final String itemId, final Variant value)
            throws InterruptedException {
        this.registrationManager.startWrite(connectionId, itemId, value,
                new DisplayCallbackHandler(this.shell, "Confirm", "Confirm write operation"));
    }

    public void startWriteAttributes(final String connectionId, final String itemId,
            final Map<String, Variant> attributes) throws InterruptedException {
        this.registrationManager.startWriteAttributes(connectionId, itemId, attributes,
                new DisplayCallbackHandler(this.shell, "Confirm", "Confirm write operation"));
    }

    public void debugLog(final String string) {
        final MessageConsoleStream stream = this.console.newMessageStream();
        final PrintWriter ps = new PrintWriter(stream);
        ps.println(string);

        try {
            ps.close();
            stream.close();
        } catch (final IOException e) {
        }
    }

    public void errorLog(final String string) {
        errorLog(string, null);
    }

    public void errorLog(final String string, final Exception e) {
        final PrintWriter pw = new PrintWriter(this.errorStream);
        pw.println(string);
        e.printStackTrace(pw);
        pw.flush();
    }

    protected SymbolContext getContext() {
        return this.context;
    }

    public SymbolContext getParentContext() {
        if (this.parentController != null) {
            return this.parentController.getContext();
        } else {
            return null;
        }
    }

}