/******************************************************************************* * Copyright (c) 2010, 2013 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation * Semantum Oy - issue #4384 *******************************************************************************/ package org.simantics.diagram.runtime; import java.util.concurrent.atomic.AtomicReference; import org.eclipse.ui.IEditorInput; import org.simantics.databoard.Bindings; import org.simantics.db.AsyncReadGraph; import org.simantics.db.RequestProcessor; import org.simantics.db.Resource; import org.simantics.db.Session; import org.simantics.db.VirtualGraph; import org.simantics.db.WriteGraph; import org.simantics.db.common.procedure.adapter.ListenerSupport; import org.simantics.db.common.request.WriteRequest; import org.simantics.db.common.request.WriteResultRequest; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.util.RemoverUtil; import org.simantics.db.layer0.variable.RVI; import org.simantics.db.procedure.AsyncListener; import org.simantics.diagram.stubs.DiagramResource; import org.simantics.layer0.Layer0; import org.simantics.operation.Layer0X; import org.simantics.ui.workbench.IResourceEditorInput2; import org.simantics.utils.datastructures.Callback; import org.simantics.utils.ui.ErrorLogger; /** * A helper class for managing the life-cycle of a diagram runtime realization. * *

* The diagram runtime resource is a virtual (transient) graph database resource * that specifies the active runtime properties of the diagram as follows: * *

 *    RUNTIME RESOURCE
 *    |---[DIAGRAM.RuntimeDiagram.HasConfiguration]---> :DIAGRAM.Diagram (directed link, no inverse relation)
 *    |---[DIAGRAM.RuntimeDiagram.HasRuntimeProfile]---> :DIAGRAM.Profile (directed link, no inverse relation)
 *    |---[DIAGRAM.HasModelURI]------------ "Model URI" : L0.String
 *    |---[DIAGRAM.HasVariable]------------ "Variable URI" : L0.String
 *    `---[DIAGRAM.HasRVI]----------------- "RVI" : L0.String
 * 
* * The runtime resource itself is not attached anywhere in the graph, it only * contains a directed link back to its diagram. * *

* For example diagram profiles require the runtime diagram to have the * DIAGRAM.HasVariable property. This in turn requires that the model has a * proper {@link Layer0X#HasBaseRealization} relation in order to be resolved * (see {@link RuntimeVariable}). * *

* To get started using this class, see * {@link #track(Session, Resource, IEditorInput, ListenerSupport)} and * {@link #create(Session, Resource, String, String)}. * *

* Instances of this class are not thread-safe. * * @author Tuukka Lehtonen */ public class RuntimeDiagramManager { private boolean disposed; private Session session; private IEditorInput editorInput; private ListenerSupport support; private AtomicReference runtimeDiagram = new AtomicReference(); /** * Constructs a new RuntimeDiagramManager, creates a new runtime diagram * based on the specified editor input if it is an * {@link IResourceEditorInput2} and starts tracking (listening) to changes * in editor input which specifies the ModelURI and RVI needed for the * runtime diagram. The tracking aspect comes to play in that editor inputs * are basically allowed to change and therefore this manager tries to * listen to changes in both the Model URI and RVI. If the input changes, * the manager will automatically update the properties of the runtime * diagram to match the input. * *

* Invoking this method is equal to calling: * *

     * RuntimeDiagramManager manager = new RuntimeDiagramManager(session);
     * manager.track(diagram, editorInput, listenerSupport);
     * 
* * @param session * @param diagram * @param editorInput * @param listenerSupport * @return * @throws DatabaseException */ public static RuntimeDiagramManager track( Session session, final Resource diagram, IEditorInput editorInput, ListenerSupport listenerSupport) throws DatabaseException { RuntimeDiagramManager manager = new RuntimeDiagramManager(session); manager.track(diagram, editorInput, listenerSupport); return manager; } /** * Just as {@link #track(Session, Resource, IEditorInput, ListenerSupport)} * this method will construct a RuntimeDiagramManager and create a runtime * diagram resource, but contrary to * {@link #track(Session, Resource, IEditorInput, ListenerSupport)} it will * not listen to changes nor update the runtime diagram properties. * *

* Invoking this method is equal to calling: *

     * RuntimeDiagramManager manager = new RuntimeDiagramManager(session);
     * manager.createRuntimeDiagram(diagram, modelURI, RVI);
     * 
* * @param session * @param diagram * @param modelURI * @param RVI * @return * @throws DatabaseException * * @see {@link #track(Session, Resource, IEditorInput, ListenerSupport)} */ public static RuntimeDiagramManager create( Session session, Resource diagram, String modelURI, String RVI) throws DatabaseException { RuntimeDiagramManager manager = new RuntimeDiagramManager(session); manager.createRuntimeDiagram(diagram, modelURI, RVI); return manager; } public RuntimeDiagramManager(Session session) { this.session = session; } public Resource getRuntimeDiagram() { return runtimeDiagram.get(); } public void dispose() { synchronized (this) { assertNotDisposed(); disposed = true; } destroy(); this.session = null; this.editorInput = null; this.support = null; } private void assertNotDisposed() { if (disposed) throw new IllegalStateException(this + " is disposed"); } private static IResourceEditorInput2 getResourceInput(Object input) { if (input instanceof IResourceEditorInput2) return (IResourceEditorInput2) input; return null; } private IResourceEditorInput2 getResourceInput() { return getResourceInput(editorInput); } /** * Does nothing if already tracking a diagram runtime resource. * * @param diagram * @param editorInput * @param listenerSupport * @return the current tracked diagram runtime resource * @throws DatabaseException * * @see {@link #track(Session, Resource, IEditorInput, ListenerSupport)} */ public Resource track(final Resource diagram, IEditorInput editorInput, ListenerSupport listenerSupport) throws DatabaseException { Resource runtime = runtimeDiagram.get(); if (runtime != null) return runtime; if (editorInput == null) throw new NullPointerException("null editorInput"); if (listenerSupport == null) throw new NullPointerException("null listenerSupport"); final IResourceEditorInput2 input = getResourceInput(editorInput); if (input == null) return null; this.editorInput = editorInput; this.support = listenerSupport; runtime = session.syncRequest(new WriteResultRequest(session.getService(VirtualGraph.class)) { @Override public Resource perform(WriteGraph graph) throws DatabaseException { RuntimeDiagramDesc variable = graph.syncRequest(new RuntimeVariableForInput(input)); if (variable == null) return null; final Resource runtime = createRuntimeDiagram(graph, diagram, variable); listenRequest(graph, diagram); return runtime; } }); runtimeDiagram.set(runtime); return runtime; } /** * @param diagram * @param modelURI * @param RVI * @return * @throws DatabaseException * * @see {@link #create(Session, Resource, String, String) */ public Resource createRuntimeDiagram(final Resource diagram, final String modelURI, final String RVI) throws DatabaseException { Resource runtime = runtimeDiagram.get(); if (runtime != null) return runtime; runtime = session.syncRequest(new WriteResultRequest(session.getService(VirtualGraph.class)) { @Override public Resource perform(WriteGraph graph) throws DatabaseException { Resource model = graph.getPossibleResource(modelURI); return createRuntimeDiagram(graph, diagram, model, RVI); } }); runtimeDiagram.set(runtime); return runtime; } /** * @param graph * @param diagram * @param modelURI * @param RVI * @return * @throws DatabaseException */ public Resource createRuntimeDiagram(WriteGraph graph, final Resource diagram, final Resource model, final String rvis) throws DatabaseException { RVI rvi = rvis != null ? RVI.fromResourceFormat(graph, rvis) : null; RuntimeDiagramDesc desc = graph.syncRequest(new RuntimeVariable(model, rvi, diagram)); if (desc == null) return null; return createRuntimeDiagram(graph, diagram, desc); } private void listenRequest(RequestProcessor processor, final Resource diagram) { processor.asyncRequest(new RuntimeVariableForInput(getResourceInput()), new AsyncListener() { @Override public void exception(AsyncReadGraph graph, Throwable throwable) { ListenerSupport s = support; if (s != null) s.exception(throwable); } @Override public void execute(AsyncReadGraph graph, final RuntimeDiagramDesc desc) { if (desc == null) return; Session session = graph.getSession(); session.asyncRequest(new WriteRequest(session.getService(VirtualGraph.class)) { @Override public void perform(WriteGraph graph) throws DatabaseException { Resource runtime = getRuntimeDiagram(); if (runtime != null) writeConfig(graph, runtime, diagram, desc); } }, new Callback() { @Override public void run(DatabaseException e) { ListenerSupport s = support; if (e != null && s != null) s.exception(e); } }); } @Override public boolean isDisposed() { if(disposed) return true; else return support != null && support.isDisposed(); } }); } private Resource createRuntimeDiagram(WriteGraph graph, Resource diagram, RuntimeDiagramDesc desc) throws DatabaseException { Layer0 L0 = Layer0.getInstance(graph); final DiagramResource DIA = DiagramResource.getInstance(graph); // Create the new runtime diagram instance! final Resource runtime = graph.newResource(); graph.claim(runtime, L0.InstanceOf, null, DIA.RuntimeDiagram); graph.claim(runtime, DIA.RuntimeDiagram_HasConfiguration, null, diagram); writeConfig(graph, runtime, diagram, desc); return runtime; } private void writeConfig(WriteGraph graph, final Resource runtime, final Resource diagram, RuntimeDiagramDesc desc) throws DatabaseException { final DiagramResource DIA = DiagramResource.getInstance(graph); if (desc.getVariableURI() != null) graph.claimLiteral(runtime, DIA.RuntimeDiagram_HasVariable, desc.getVariableURI(), Bindings.STRING); if (desc.getRVI() != null) graph.claimLiteral(runtime, DIA.RuntimeDiagram_HasRVI, desc.getRVI(), Bindings.STRING); if (desc.getModelURI() != null) graph.claimLiteral(runtime, DIA.RuntimeDiagram_HasModelURI, desc.getModelURI(), Bindings.STRING); if (desc.getActiveProfileURI() != null) { Resource activeProfile = graph.getPossibleResource(desc.getActiveProfileURI()); if (activeProfile != null) { graph.deny(runtime, DIA.RuntimeDiagram_HasRuntimeProfile); graph.claim(runtime, DIA.RuntimeDiagram_HasRuntimeProfile, null, activeProfile); } } } private void destroy() { final Resource rd = runtimeDiagram.getAndSet(null); if (rd != null) { try { session.syncRequest(new WriteRequest(session.getService(VirtualGraph.class)) { @Override public void perform(WriteGraph graph) throws DatabaseException { RemoverUtil.remove(graph, rd); } }); } catch (DatabaseException e) { ListenerSupport s = support; if (s != null) s.exception(e); else ErrorLogger.defaultLogError(e); } } } }