--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2010, 2013 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ * Semantum Oy - issue #4384\r
+ *******************************************************************************/\r
+package org.simantics.diagram.runtime;\r
+\r
+import java.util.concurrent.atomic.AtomicReference;\r
+\r
+import org.eclipse.ui.IEditorInput;\r
+import org.simantics.databoard.Bindings;\r
+import org.simantics.db.AsyncReadGraph;\r
+import org.simantics.db.RequestProcessor;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.Session;\r
+import org.simantics.db.VirtualGraph;\r
+import org.simantics.db.WriteGraph;\r
+import org.simantics.db.common.procedure.adapter.ListenerSupport;\r
+import org.simantics.db.common.request.WriteRequest;\r
+import org.simantics.db.common.request.WriteResultRequest;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.layer0.util.RemoverUtil;\r
+import org.simantics.db.layer0.variable.RVI;\r
+import org.simantics.db.procedure.AsyncListener;\r
+import org.simantics.diagram.stubs.DiagramResource;\r
+import org.simantics.layer0.Layer0;\r
+import org.simantics.operation.Layer0X;\r
+import org.simantics.ui.workbench.IResourceEditorInput2;\r
+import org.simantics.utils.datastructures.Callback;\r
+import org.simantics.utils.ui.ErrorLogger;\r
+\r
+/**\r
+ * A helper class for managing the life-cycle of a diagram runtime realization.\r
+ * \r
+ * <p>\r
+ * The diagram runtime resource is a virtual (transient) graph database resource\r
+ * that specifies the active runtime properties of the diagram as follows:\r
+ * \r
+ * <pre>\r
+ * RUNTIME RESOURCE\r
+ * |---[DIAGRAM.RuntimeDiagram.HasConfiguration]---> :DIAGRAM.Diagram (directed link, no inverse relation)\r
+ * |---[DIAGRAM.RuntimeDiagram.HasRuntimeProfile]---> :DIAGRAM.Profile (directed link, no inverse relation)\r
+ * |---[DIAGRAM.HasModelURI]------------ "Model URI" : L0.String\r
+ * |---[DIAGRAM.HasVariable]------------ "Variable URI" : L0.String\r
+ * `---[DIAGRAM.HasRVI]----------------- "RVI" : L0.String\r
+ * </pre>\r
+ * \r
+ * The runtime resource itself is not attached anywhere in the graph, it only\r
+ * contains a directed link back to its diagram.\r
+ * \r
+ * <p>\r
+ * For example diagram profiles require the runtime diagram to have the\r
+ * DIAGRAM.HasVariable property. This in turn requires that the model has a\r
+ * proper {@link Layer0X#HasBaseRealization} relation in order to be resolved\r
+ * (see {@link RuntimeVariable}).\r
+ * \r
+ * <p>\r
+ * To get started using this class, see\r
+ * {@link #track(Session, Resource, IEditorInput, ListenerSupport)} and\r
+ * {@link #create(Session, Resource, String, String)}.\r
+ * \r
+ * <p>\r
+ * Instances of this class are not thread-safe.\r
+ * \r
+ * @author Tuukka Lehtonen\r
+ */\r
+public class RuntimeDiagramManager {\r
+\r
+ private boolean disposed;\r
+\r
+ private Session session;\r
+ private IEditorInput editorInput;\r
+ private ListenerSupport support;\r
+\r
+ private AtomicReference<Resource> runtimeDiagram = new AtomicReference<Resource>();\r
+\r
+ /**\r
+ * Constructs a new RuntimeDiagramManager, creates a new runtime diagram\r
+ * based on the specified editor input if it is an\r
+ * {@link IResourceEditorInput2} and starts tracking (listening) to changes\r
+ * in editor input which specifies the ModelURI and RVI needed for the\r
+ * runtime diagram. The tracking aspect comes to play in that editor inputs\r
+ * are basically allowed to change and therefore this manager tries to\r
+ * listen to changes in both the Model URI and RVI. If the input changes,\r
+ * the manager will automatically update the properties of the runtime\r
+ * diagram to match the input.\r
+ * \r
+ * <p>\r
+ * Invoking this method is equal to calling:\r
+ * \r
+ * <pre>\r
+ * RuntimeDiagramManager manager = new RuntimeDiagramManager(session);\r
+ * manager.track(diagram, editorInput, listenerSupport);\r
+ * </pre>\r
+ * \r
+ * @param session\r
+ * @param diagram\r
+ * @param editorInput\r
+ * @param listenerSupport\r
+ * @return\r
+ * @throws DatabaseException\r
+ */\r
+ public static RuntimeDiagramManager track(\r
+ Session session,\r
+ final Resource diagram,\r
+ IEditorInput editorInput,\r
+ ListenerSupport listenerSupport)\r
+ throws DatabaseException\r
+ {\r
+ RuntimeDiagramManager manager = new RuntimeDiagramManager(session);\r
+ manager.track(diagram, editorInput, listenerSupport);\r
+ return manager;\r
+ }\r
+\r
+ /**\r
+ * Just as {@link #track(Session, Resource, IEditorInput, ListenerSupport)}\r
+ * this method will construct a RuntimeDiagramManager and create a runtime\r
+ * diagram resource, but contrary to\r
+ * {@link #track(Session, Resource, IEditorInput, ListenerSupport)} it will\r
+ * not listen to changes nor update the runtime diagram properties.\r
+ * \r
+ * <p>\r
+ * Invoking this method is equal to calling:\r
+ * <pre>\r
+ * RuntimeDiagramManager manager = new RuntimeDiagramManager(session);\r
+ * manager.createRuntimeDiagram(diagram, modelURI, RVI);\r
+ * </pre>\r
+ * \r
+ * @param session\r
+ * @param diagram\r
+ * @param modelURI\r
+ * @param RVI\r
+ * @return\r
+ * @throws DatabaseException\r
+ * \r
+ * @see {@link #track(Session, Resource, IEditorInput, ListenerSupport)}\r
+ */\r
+ public static RuntimeDiagramManager create(\r
+ Session session,\r
+ Resource diagram,\r
+ String modelURI,\r
+ String RVI)\r
+ throws DatabaseException\r
+ {\r
+ RuntimeDiagramManager manager = new RuntimeDiagramManager(session);\r
+ manager.createRuntimeDiagram(diagram, modelURI, RVI);\r
+ return manager;\r
+ }\r
+\r
+ public RuntimeDiagramManager(Session session) {\r
+ this.session = session;\r
+ }\r
+\r
+ public Resource getRuntimeDiagram() {\r
+ return runtimeDiagram.get();\r
+ }\r
+\r
+ public void dispose() {\r
+ synchronized (this) {\r
+ assertNotDisposed();\r
+ disposed = true;\r
+ }\r
+\r
+ destroy();\r
+\r
+ this.session = null;\r
+ this.editorInput = null;\r
+ this.support = null;\r
+ }\r
+\r
+ private void assertNotDisposed() {\r
+ if (disposed)\r
+ throw new IllegalStateException(this + " is disposed");\r
+ }\r
+\r
+ private static IResourceEditorInput2 getResourceInput(Object input) {\r
+ if (input instanceof IResourceEditorInput2)\r
+ return (IResourceEditorInput2) input;\r
+ return null;\r
+ }\r
+\r
+ private IResourceEditorInput2 getResourceInput() {\r
+ return getResourceInput(editorInput);\r
+ }\r
+\r
+ /**\r
+ * Does nothing if already tracking a diagram runtime resource.\r
+ * \r
+ * @param diagram\r
+ * @param editorInput\r
+ * @param listenerSupport\r
+ * @return the current tracked diagram runtime resource\r
+ * @throws DatabaseException\r
+ * \r
+ * @see {@link #track(Session, Resource, IEditorInput, ListenerSupport)}\r
+ */\r
+ public Resource track(final Resource diagram, IEditorInput editorInput, ListenerSupport listenerSupport) throws DatabaseException {\r
+ Resource runtime = runtimeDiagram.get();\r
+ if (runtime != null)\r
+ return runtime;\r
+\r
+ if (editorInput == null)\r
+ throw new NullPointerException("null editorInput");\r
+ if (listenerSupport == null)\r
+ throw new NullPointerException("null listenerSupport");\r
+\r
+ final IResourceEditorInput2 input = getResourceInput(editorInput);\r
+ if (input == null)\r
+ return null;\r
+\r
+ this.editorInput = editorInput;\r
+ this.support = listenerSupport;\r
+\r
+ runtime = session.syncRequest(new WriteResultRequest<Resource>(session.getService(VirtualGraph.class)) {\r
+ @Override\r
+ public Resource perform(WriteGraph graph) throws DatabaseException {\r
+ RuntimeDiagramDesc variable = graph.syncRequest(new RuntimeVariableForInput(input));\r
+ if (variable == null)\r
+ return null;\r
+\r
+ final Resource runtime = createRuntimeDiagram(graph, diagram, variable);\r
+ listenRequest(graph, diagram);\r
+ return runtime;\r
+ }\r
+ });\r
+\r
+ runtimeDiagram.set(runtime);\r
+ return runtime;\r
+ }\r
+\r
+ /**\r
+ * @param diagram\r
+ * @param modelURI\r
+ * @param RVI\r
+ * @return\r
+ * @throws DatabaseException\r
+ * \r
+ * @see {@link #create(Session, Resource, String, String)\r
+ */\r
+ public Resource createRuntimeDiagram(final Resource diagram, final String modelURI, final String RVI) throws DatabaseException {\r
+ Resource runtime = runtimeDiagram.get();\r
+ if (runtime != null)\r
+ return runtime;\r
+\r
+ runtime = session.syncRequest(new WriteResultRequest<Resource>(session.getService(VirtualGraph.class)) {\r
+ @Override\r
+ public Resource perform(WriteGraph graph) throws DatabaseException {\r
+ Resource model = graph.getPossibleResource(modelURI);\r
+ return createRuntimeDiagram(graph, diagram, model, RVI);\r
+ }\r
+ });\r
+\r
+ runtimeDiagram.set(runtime);\r
+ return runtime;\r
+ }\r
+\r
+ /**\r
+ * @param graph\r
+ * @param diagram\r
+ * @param modelURI\r
+ * @param RVI\r
+ * @return\r
+ * @throws DatabaseException\r
+ */\r
+ public Resource createRuntimeDiagram(WriteGraph graph, final Resource diagram, final Resource model, final String rvis) throws DatabaseException {\r
+ RVI rvi = rvis != null ? RVI.fromResourceFormat(graph, rvis) : null;\r
+ RuntimeDiagramDesc desc = graph.syncRequest(new RuntimeVariable(model, rvi, diagram));\r
+ if (desc == null)\r
+ return null;\r
+ return createRuntimeDiagram(graph, diagram, desc);\r
+ }\r
+\r
+ private void listenRequest(RequestProcessor processor, final Resource diagram) {\r
+ processor.asyncRequest(new RuntimeVariableForInput(getResourceInput()), new AsyncListener<RuntimeDiagramDesc>() {\r
+\r
+ @Override\r
+ public void exception(AsyncReadGraph graph, Throwable throwable) {\r
+ ListenerSupport s = support;\r
+ if (s != null)\r
+ s.exception(throwable);\r
+ }\r
+\r
+ @Override\r
+ public void execute(AsyncReadGraph graph, final RuntimeDiagramDesc desc) {\r
+ if (desc == null)\r
+ return;\r
+\r
+ Session session = graph.getSession();\r
+ session.asyncRequest(new WriteRequest(session.getService(VirtualGraph.class)) {\r
+ @Override\r
+ public void perform(WriteGraph graph) throws DatabaseException {\r
+ Resource runtime = getRuntimeDiagram();\r
+ if (runtime != null)\r
+ writeConfig(graph, runtime, diagram, desc);\r
+ }\r
+ }, new Callback<DatabaseException>() {\r
+ @Override\r
+ public void run(DatabaseException e) {\r
+ ListenerSupport s = support;\r
+ if (e != null && s != null)\r
+ s.exception(e);\r
+ }\r
+ });\r
+ }\r
+\r
+ @Override\r
+ public boolean isDisposed() {\r
+ if(disposed) return true;\r
+ else return support != null && support.isDisposed();\r
+ }\r
+ });\r
+ }\r
+\r
+ private Resource createRuntimeDiagram(WriteGraph graph, Resource diagram, RuntimeDiagramDesc desc) throws DatabaseException {\r
+\r
+ Layer0 L0 = Layer0.getInstance(graph);\r
+ final DiagramResource DIA = DiagramResource.getInstance(graph);\r
+\r
+ // Create the new runtime diagram instance!\r
+ final Resource runtime = graph.newResource();\r
+ graph.claim(runtime, L0.InstanceOf, null, DIA.RuntimeDiagram);\r
+ graph.claim(runtime, DIA.RuntimeDiagram_HasConfiguration, null, diagram);\r
+\r
+ writeConfig(graph, runtime, diagram, desc);\r
+\r
+ return runtime;\r
+ }\r
+\r
+ private void writeConfig(WriteGraph graph, final Resource runtime, final Resource diagram, RuntimeDiagramDesc desc) throws DatabaseException {\r
+ final DiagramResource DIA = DiagramResource.getInstance(graph);\r
+ if (desc.getVariableURI() != null)\r
+ graph.claimLiteral(runtime, DIA.RuntimeDiagram_HasVariable, desc.getVariableURI(), Bindings.STRING);\r
+ if (desc.getRVI() != null)\r
+ graph.claimLiteral(runtime, DIA.RuntimeDiagram_HasRVI, desc.getRVI(), Bindings.STRING);\r
+ if (desc.getModelURI() != null)\r
+ graph.claimLiteral(runtime, DIA.RuntimeDiagram_HasModelURI, desc.getModelURI(), Bindings.STRING);\r
+ if (desc.getActiveProfileURI() != null) {\r
+ Resource activeProfile = graph.getPossibleResource(desc.getActiveProfileURI());\r
+ if (activeProfile != null) {\r
+ graph.deny(runtime, DIA.RuntimeDiagram_HasRuntimeProfile);\r
+ graph.claim(runtime, DIA.RuntimeDiagram_HasRuntimeProfile, null, activeProfile);\r
+ }\r
+ }\r
+ }\r
+\r
+ private void destroy() {\r
+ final Resource rd = runtimeDiagram.getAndSet(null);\r
+ if (rd != null) {\r
+ try {\r
+ session.syncRequest(new WriteRequest(session.getService(VirtualGraph.class)) {\r
+ @Override\r
+ public void perform(WriteGraph graph) throws DatabaseException {\r
+ RemoverUtil.remove(graph, rd);\r
+ }\r
+ });\r
+ } catch (DatabaseException e) {\r
+ ListenerSupport s = support;\r
+ if (s != null)\r
+ s.exception(e);\r
+ else\r
+ ErrorLogger.defaultLogError(e);\r
+ }\r
+ }\r
+ }\r
+\r
+}\r