-/*******************************************************************************\r
- * Copyright (c) 2007, 2011 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
- *******************************************************************************/\r
-package org.simantics.issues.common;\r
-\r
-import gnu.trove.set.hash.THashSet;\r
-\r
-import java.text.DateFormat;\r
-import java.text.SimpleDateFormat;\r
-import java.util.Collection;\r
-import java.util.Collections;\r
-import java.util.Date;\r
-import java.util.HashMap;\r
-import java.util.List;\r
-import java.util.Map;\r
-import java.util.Set;\r
-import java.util.UUID;\r
-import java.util.concurrent.atomic.AtomicBoolean;\r
-\r
-import org.simantics.Simantics;\r
-import org.simantics.databoard.Bindings;\r
-import org.simantics.databoard.util.URIStringUtils;\r
-import org.simantics.db.Disposable;\r
-import org.simantics.db.Issue;\r
-import org.simantics.db.ReadGraph;\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.primitiverequest.Objects;\r
-import org.simantics.db.common.procedure.adapter.DisposableSyncListener;\r
-import org.simantics.db.common.procedure.adapter.TransientCacheListener;\r
-import org.simantics.db.common.procedure.single.SingleSetSyncListener;\r
-import org.simantics.db.common.request.ResourceRead3;\r
-import org.simantics.db.common.request.WriteRequest;\r
-import org.simantics.db.common.utils.ListUtils;\r
-import org.simantics.db.exception.DatabaseException;\r
-import org.simantics.db.layer0.request.ActiveModels;\r
-import org.simantics.db.layer0.request.Model;\r
-import org.simantics.db.layer0.request.PossibleModel;\r
-import org.simantics.db.layer0.util.RemoverUtil;\r
-import org.simantics.db.layer0.variable.Variable;\r
-import org.simantics.db.service.VirtualGraphSupport;\r
-import org.simantics.issues.Severity;\r
-import org.simantics.issues.ontology.IssueResource;\r
-import org.simantics.layer0.Layer0;\r
-import org.simantics.operation.Layer0X;\r
-import org.simantics.scl.runtime.function.FunctionImpl2;\r
-import org.simantics.utils.datastructures.Pair;\r
-import org.slf4j.Logger;\r
-import org.slf4j.LoggerFactory;\r
-\r
-/**\r
- * @author Tuukka Lehtonen\r
- */\r
-public class IssueUtils {\r
- private static final Logger LOGGER = LoggerFactory.getLogger(IssueUtils.class);\r
-\r
- public static Resource toSeverityResource(IssueResource ISSUE, Severity severity) {\r
- switch (severity) {\r
- case ERROR: return ISSUE.Severity_Error;\r
- case FATAL: return ISSUE.Severity_Fatal;\r
- case INFO: return ISSUE.Severity_Info;\r
- case WARNING: return ISSUE.Severity_Warning;\r
- case NOTE: return ISSUE.Severity_Note;\r
- default: return null;\r
- }\r
- }\r
-\r
- public static Severity toSeverity(IssueResource ISSUE, Resource severity) {\r
- if (severity == null)\r
- return null;\r
- if (severity.equals(ISSUE.Severity_Fatal))\r
- return Severity.FATAL;\r
- if (severity.equals(ISSUE.Severity_Error))\r
- return Severity.ERROR;\r
- if (severity.equals(ISSUE.Severity_Info))\r
- return Severity.INFO;\r
- if (severity.equals(ISSUE.Severity_Warning))\r
- return Severity.WARNING;\r
- if (severity.equals(ISSUE.Severity_Note))\r
- return Severity.NOTE;\r
- return null;\r
- }\r
-\r
- private static class IssueSourceDirtyListener extends FunctionImpl2<ReadGraph, List<Resource>, Boolean> {\r
-\r
- private final IssueSource is;\r
-\r
- public IssueSourceDirtyListener(IssueSource is) {\r
- this.is = is;\r
- }\r
-\r
- @Override\r
- public Boolean apply(ReadGraph graph, final List<Resource> resources) {\r
- VirtualGraphSupport support = graph.getService(VirtualGraphSupport.class);\r
- VirtualGraph vg = support.getWorkspacePersistent(IssueConstants.ISSUE_VG);\r
- if (graph instanceof WriteGraph) {\r
- try {\r
- if(is.needUpdate(graph, resources)) {\r
- graph.sync(new WriteRequest(vg) {\r
- @Override\r
- public void perform(WriteGraph graph) throws DatabaseException {\r
- is.update(graph, resources);\r
- }\r
- });\r
- }\r
- } catch (DatabaseException e) {\r
- LOGGER.error("Updating issue source failed.", e);\r
- }\r
- } else {\r
- Session session = Simantics.getSession();\r
- session.asyncRequest(new WriteRequest(vg) {\r
- @Override\r
- public void perform(WriteGraph graph) throws DatabaseException {\r
- is.update(graph, resources);\r
- }\r
- });\r
- }\r
-\r
- return true;\r
- }\r
-\r
- }\r
-\r
- private static class IssueSourceManagedIssuesListener extends SingleSetSyncListener<Resource> {\r
-\r
- private final HashMap<Resource, IssueValidityListener> listeners = new HashMap<>();\r
- private final AtomicBoolean disposed;\r
- private final Resource source;\r
- private final Resource model;\r
-\r
- public IssueSourceManagedIssuesListener(AtomicBoolean disposed, Resource source, Resource model) {\r
- this.disposed = disposed;\r
- this.source = source;\r
- this.model = model;\r
- }\r
-\r
- class IssueValidityListener extends DisposableSyncListener<Boolean> {\r
-\r
- final private Resource issue;\r
-\r
- public IssueValidityListener(Resource issue) {\r
- this.issue = issue;\r
- }\r
-\r
- @Override\r
- public void execute(ReadGraph graph, Boolean valid) throws DatabaseException {\r
- if(!valid) {\r
- VirtualGraphSupport support = graph.getService(VirtualGraphSupport.class);\r
- VirtualGraph vg = support.getWorkspacePersistent(IssueConstants.ISSUE_VG);\r
- graph.asyncRequest(new WriteRequest(vg) {\r
-\r
- @Override\r
- public void perform(WriteGraph graph) throws DatabaseException {\r
- Issue desc = graph.sync(new StandardIssueDescription(issue));\r
- if(desc == null)\r
- return;\r
- Resource context = (Resource)desc.getMainContext();\r
- new DependencyIssueSynchronizer2(context, source).perform(graph);\r
- }\r
- \r
- });\r
- }\r
- }\r
-\r
- @Override\r
- public void exception(ReadGraph graph, Throwable throwable) throws DatabaseException {\r
- LOGGER.error("IssueValidityListener received an exception.", throwable);\r
- }\r
-\r
- }\r
-\r
- @Override\r
- public void add(ReadGraph graph, final Resource issue) throws DatabaseException {\r
- IssueValidityListener listener = new IssueValidityListener(issue);\r
-\r
- graph.asyncRequest(new ResourceRead3<Boolean>(issue, model, source) {\r
-\r
- @Override\r
- public Boolean perform(ReadGraph graph) throws DatabaseException {\r
- Issue desc = graph.sync(new StandardIssueDescription(resource));\r
- if(desc == null)\r
- return false;\r
- Resource context = (Resource)desc.getMainContext();\r
- return graph.syncRequest(new DependencyIssueValidator2(context, resource2, resource3), TransientCacheListener.<Boolean>instance());\r
- }\r
-\r
- }, listener);\r
-\r
- listeners.put(issue, listener);\r
- }\r
-\r
- @Override\r
- public void remove(ReadGraph graph, final Resource issue) throws DatabaseException {\r
- IssueValidityListener listener = listeners.remove(issue);\r
- if(listener != null)\r
- listener.dispose();\r
- }\r
-\r
- @Override\r
- public void exception(ReadGraph graph, Throwable t) {\r
- LOGGER.error("IssueSourceManagedIssuesListener received an exception.", t);\r
- }\r
-\r
- @Override\r
- public boolean isDisposed() {\r
- boolean disp = disposed.get();\r
- if (disp) {\r
- // Ensure validity listeners are cleared eventually.\r
- if (!listeners.isEmpty()) {\r
- for (IssueValidityListener listener : listeners.values()) {\r
- listener.dispose();\r
- }\r
- listeners.clear();\r
- }\r
- }\r
- return disp;\r
- }\r
-\r
- }\r
-\r
- private static class ActiveIssueSourceListener extends SingleSetSyncListener<Resource> {\r
-\r
- private final AtomicBoolean disposed;\r
- private Map<Resource, Pair<IssueSource, IssueSourceDirtyListener>> sources = new HashMap<>();\r
-\r
- public ActiveIssueSourceListener(AtomicBoolean disposed) {\r
- this.disposed = disposed;\r
- }\r
-\r
- @Override\r
- public void add(ReadGraph graph, final Resource source) throws DatabaseException {\r
- IssueResource ISSUE = IssueResource.getInstance(graph);\r
- boolean isListeningTracker = graph.isInstanceOf(source, ISSUE.Sources_ListeningDependencyTracker);\r
- IssueSource is = graph.adapt(source, IssueSource.class);\r
- final Resource model = isListeningTracker ? graph.syncRequest(new Model(source)) : null;\r
-\r
- IssueSourceDirtyListener listener = new IssueSourceDirtyListener(is);\r
- is.addDirtyListener(listener);\r
- sources.put(source, Pair.make(is, listener));\r
-\r
- if (isListeningTracker) {\r
- graph.asyncRequest(\r
- new Objects(source, ISSUE.IssueSource_Manages),\r
- new IssueSourceManagedIssuesListener(disposed, source, model));\r
- }\r
- }\r
-\r
- @Override\r
- public void remove(ReadGraph graph, final Resource source) throws DatabaseException {\r
- Pair<IssueSource, IssueSourceDirtyListener> is = sources.remove(source);\r
- if (is != null)\r
- is.first.removeDirtyListener(is.second);\r
- }\r
-\r
- @Override\r
- public void exception(ReadGraph graph, Throwable t) {\r
- LOGGER.error("ActiveIssueSourceListener received an exception.", t);\r
- }\r
-\r
- @Override\r
- public boolean isDisposed() {\r
- return disposed.get();\r
- }\r
-\r
- }\r
-\r
- public static Disposable listenActiveProjectIssueSources(RequestProcessor processor, Resource project) throws DatabaseException {\r
- final AtomicBoolean disposed = new AtomicBoolean(false);\r
- processor.syncRequest(\r
- new ActiveProjectIssueSources(project),\r
- new ActiveIssueSourceListener(disposed));\r
- return new Disposable() {\r
- @Override\r
- public void dispose() {\r
- disposed.set(true);\r
- }\r
- };\r
- }\r
-\r
- public static List<Resource> getContextsForProperty(ReadGraph graph, Variable property) throws DatabaseException {\r
- IssueResource ISSUE = IssueResource.getInstance(graph);\r
- Variable issueVariable = property.getParent(graph);\r
- Resource issueResource = issueVariable.getRepresents(graph);\r
- Resource list = graph.getPossibleObject(issueResource, ISSUE.Issue_HasContexts);\r
- if(list != null)\r
- return ListUtils.toList(graph, list);\r
- else\r
- return Collections.emptyList();\r
- }\r
- \r
- public static void writeAdditionalContext(WriteGraph graph, Resource issue, List<Resource> contexts) throws DatabaseException {\r
-\r
- if(contexts.isEmpty()) return;\r
- \r
- IssueResource IR = IssueResource.getInstance(graph);\r
-\r
- // The main context\r
- graph.claim(issue, IR.Issue_HasContext, contexts.get(0));\r
- // A possible parent\r
- Layer0 L0 = Layer0.getInstance(graph);\r
- Resource parent = graph.getPossibleObject(contexts.get(0), L0.PartOf);\r
- if(parent != null) {\r
- graph.claim(issue, IR.Issue_HasContext, parent);\r
- }\r
- \r
- } \r
- public static void newUserIssue(WriteGraph graph, String label, Resource severity, List<Resource> contexts) throws DatabaseException {\r
- \r
- Resource model = graph.sync(new PossibleModel(contexts.get(0)));\r
- if(model == null) throw new DatabaseException("No model for main context");\r
- \r
- newUserIssueForModel(graph, model, label, severity, contexts);\r
- \r
- }\r
- \r
- public static Resource newUserIssueForModel(WriteGraph graph) throws DatabaseException {\r
- \r
- Resource project = Simantics.getProjectResource();\r
- Collection<Resource> activeModels = graph.syncRequest(new ActiveModels(project));\r
- if (activeModels.size() != 1)\r
- return null;\r
-\r
- IssueResource ISSUE = IssueResource.getInstance(graph);\r
- Resource issue = null;\r
- for (Resource model : activeModels) {\r
- issue = newUserIssueForModel(graph, model, "New User Issue", ISSUE.Severity_Note, Collections.<Resource>emptyList());\r
- }\r
- return issue;\r
- \r
- }\r
- \r
-\r
- public static Resource newUserIssueForModel(WriteGraph graph, Resource model, String label, Resource severity, List<Resource> contexts) throws DatabaseException {\r
- IssueResource ISSUE = IssueResource.getInstance(graph);\r
- return newUserIssueForModel(graph, model, ISSUE.Issue, label, severity, contexts);\r
- }\r
-\r
- public static Resource newUserIssueForModel(WriteGraph graph, Resource model, Resource type, String label, Resource severity, List<Resource> contexts) throws DatabaseException {\r
-\r
- Layer0 L0 = Layer0.getInstance(graph);\r
- IssueResource ISSUE = IssueResource.getInstance(graph);\r
- \r
- Resource issue = graph.newResource();\r
- graph.claim(issue, ISSUE.UserIssue, ISSUE.UserIssue, issue);\r
- graph.claim(issue, L0.InstanceOf, null, type);\r
- graph.claim(issue, ISSUE.Issue_HasSeverity, null, severity);\r
- graph.claim(issue, ISSUE.Issue_HasContexts, ListUtils.create(graph, L0.List, contexts));\r
- writeAdditionalContext(graph, issue, contexts);\r
- graph.claimLiteral(issue, L0.HasName, UUID.randomUUID().toString(), Bindings.STRING);\r
- graph.claimLiteral(issue, L0.HasLabel, label, Bindings.STRING);\r
- DateFormat format = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");\r
- Object time = format.format(new Date());\r
- graph.claimLiteral(issue, ISSUE.Issue_creationTime, time, Bindings.STRING);\r
- graph.claim(model, L0.ConsistsOf, L0.PartOf, issue);\r
- return issue;\r
- \r
- }\r
- \r
- /**\r
- * Creates a new issue based on SimpleIssue structure.\r
- */\r
- public static void newSimpleIssueForModel(WriteGraph graph, Resource model, Resource issueType, List<Resource> contexts, SimpleIssue simpleIssue) throws DatabaseException {\r
-\r
- Layer0 L0 = Layer0.getInstance(graph);\r
- IssueResource ISSUE = IssueResource.getInstance(graph);\r
- \r
- Resource issue = graph.newResource();\r
- graph.claim(issue, L0.InstanceOf, null, issueType);\r
- graph.claim(issue, ISSUE.Issue_HasSeverity, null, toSeverityResource(ISSUE, simpleIssue.severity));\r
- graph.claim(issue, ISSUE.Issue_HasContexts, ListUtils.create(graph, L0.List, contexts));\r
- writeAdditionalContext(graph, issue, contexts);\r
- graph.claimLiteral(issue, L0.HasName, UUID.randomUUID().toString(), Bindings.STRING);\r
- graph.claimLiteral(issue, L0.HasLabel, simpleIssue.label, Bindings.STRING);\r
- DateFormat format = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");\r
- Object time = format.format(new Date());\r
- graph.claimLiteral(issue, ISSUE.Issue_creationTime, time, Bindings.STRING);\r
- graph.claim(model, L0.ConsistsOf, L0.PartOf, issue);\r
- \r
- }\r
- \r
- /**\r
- * Returns the set of all issues with given type.\r
- */\r
- public static Set<SimpleIssue> getSimpleIssues(ReadGraph graph, List<Resource> contexts, Resource issueType)\r
- throws DatabaseException {\r
- Layer0 L0 = Layer0.getInstance(graph);\r
- IssueResource ISSUE = IssueResource.getInstance(graph);\r
- \r
- THashSet<SimpleIssue> currentIssues = null;\r
- for(Resource issue : graph.getObjects(contexts.get(0), ISSUE.Issue_HasContext_Inverse)) {\r
- if(!graph.isInstanceOf(issue, issueType))\r
- continue;\r
- List<Resource> curContexts = \r
- ListUtils.toList(graph, graph.getSingleObject(issue, ISSUE.Issue_HasContexts));\r
- if(!contexts.equals(curContexts))\r
- continue;\r
- if(currentIssues == null)\r
- currentIssues = new THashSet<SimpleIssue>();\r
- currentIssues.add(new SimpleIssue(\r
- (String)graph.getRelatedValue(issue, L0.HasLabel),\r
- toSeverity(ISSUE, graph.getSingleObject(issue, ISSUE.Issue_HasSeverity)), \r
- issue));\r
- }\r
- if(currentIssues == null)\r
- return Collections.emptySet();\r
- else\r
- return currentIssues;\r
- }\r
- \r
- /**\r
- * Creates and removes issues so that after the operation, \r
- * the context has exactly the given issues with the given type.\r
- */\r
- public static void setSimpleIssues(WriteGraph graph, Resource model, List<Resource> contexts, Resource issueType, SimpleIssue ... issues) throws DatabaseException {\r
- Set<SimpleIssue> currentIssues = getSimpleIssues(graph, contexts, issueType);\r
- for(SimpleIssue newIssue : issues) {\r
- if(currentIssues.contains(newIssue))\r
- currentIssues.remove(newIssue);\r
- else\r
- newSimpleIssueForModel(graph, model, issueType, contexts, newIssue);\r
- }\r
- for(SimpleIssue oldIssue : currentIssues)\r
- RemoverUtil.remove(graph, oldIssue.issueResource);\r
- }\r
- \r
- /**\r
- * Creates and removes issues so that after the operation, \r
- * the context has exactly the given issues with the given type.\r
- */\r
- public static void setSimpleIssues(WriteGraph graph, List<Resource> contexts, Resource issueType, SimpleIssue ... issues) throws DatabaseException {\r
- Resource model = graph.sync(new PossibleModel(contexts.get(0)));\r
- if(model == null) throw new DatabaseException("No model for main context");\r
- \r
- setSimpleIssues(graph, model, contexts, issueType, issues);\r
- }\r
- \r
- /**\r
- * Creates and removes issues so that after the operation, \r
- * the context has exactly the given issues with the given type.\r
- * Because this method is called in read transaction, it \r
- * makes a write transaction if necessary and continues the\r
- * operation there.\r
- */\r
- public static void setSimpleIssuesAsync(ReadGraph graph,\r
- final List<Resource> contexts,\r
- final Resource issueType, \r
- final SimpleIssue ... issues) throws DatabaseException {\r
- Resource model = graph.sync(new PossibleModel(contexts.get(0)));\r
- if(model == null) throw new DatabaseException("No model for main context");\r
- \r
- setSimpleIssuesAsync(graph, model, contexts, issueType, issues);\r
- }\r
- \r
- /**\r
- * Creates and removes issues so that after the operation, \r
- * the context has exactly the given issues with the given type.\r
- * Because this method is called in read transaction, it \r
- * makes a write transaction if necessary and continues the\r
- * operation there.\r
- */\r
- public static void setSimpleIssuesAsync(ReadGraph graph, \r
- final Resource model, \r
- final List<Resource> contexts,\r
- final Resource issueType, \r
- final SimpleIssue ... issues) throws DatabaseException {\r
- Set<SimpleIssue> oldIssues = getSimpleIssues(graph, contexts, issueType);\r
- \r
- boolean needsUpdating = false;\r
- if(issues.length != oldIssues.size())\r
- needsUpdating = true;\r
- else \r
- for(SimpleIssue newIssue : issues) {\r
- if(!oldIssues.contains(newIssue)) {\r
- needsUpdating = true;\r
- break;\r
- }\r
- }\r
- if(needsUpdating) {\r
- VirtualGraphSupport support = graph.getService(VirtualGraphSupport.class);\r
- VirtualGraph vg = support.getWorkspacePersistent(IssueConstants.ISSUE_VG);\r
- Simantics.getSession().asyncRequest(new WriteRequest(vg) {\r
- \r
- @Override\r
- public void perform(WriteGraph graph) throws DatabaseException {\r
- setSimpleIssues(graph, model, contexts, issueType, issues);\r
- }\r
- });\r
- }\r
- }\r
- \r
- public static Resource addIssueSource(WriteGraph g, Resource model, Resource sourceType, String name) throws DatabaseException {\r
- Layer0 L0 = Layer0.getInstance(g);\r
- Layer0X L0X = Layer0X.getInstance(g);\r
-\r
- Resource source = g.newResource();\r
- g.claim(source, L0.InstanceOf, null, sourceType);\r
- g.addLiteral(source, L0.HasName, L0.NameOf, L0.String, name, Bindings.STRING);\r
- g.claim(source, L0X.IsActivatedBy, L0X.Activates, model);\r
- g.claim(source, L0.PartOf, L0.ConsistsOf, model);\r
- return source;\r
- }\r
-\r
- public static String pathString(String uri, int startIndex) {\r
- StringBuilder sb = new StringBuilder(uri.length() - startIndex + 1);\r
- sb.append(URIStringUtils.NAMESPACE_PATH_SEPARATOR);\r
- while (true) {\r
- int nextSlash = uri.indexOf(URIStringUtils.NAMESPACE_PATH_SEPARATOR, startIndex);\r
- if (nextSlash == -1) {\r
- sb.append(URIStringUtils.unescape(uri.substring(startIndex, uri.length())));\r
- break;\r
- }\r
- sb.append(URIStringUtils.unescape(uri.substring(startIndex, nextSlash))).append(URIStringUtils.NAMESPACE_PATH_SEPARATOR);\r
- startIndex = nextSlash + 1;\r
- }\r
- return sb.toString();\r
- }\r
-\r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2011 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
+ *******************************************************************************/
+package org.simantics.issues.common;
+
+import gnu.trove.set.hash.THashSet;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.simantics.Simantics;
+import org.simantics.databoard.Bindings;
+import org.simantics.databoard.util.URIStringUtils;
+import org.simantics.db.Disposable;
+import org.simantics.db.Issue;
+import org.simantics.db.ReadGraph;
+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.primitiverequest.Objects;
+import org.simantics.db.common.procedure.adapter.DisposableSyncListener;
+import org.simantics.db.common.procedure.adapter.TransientCacheListener;
+import org.simantics.db.common.procedure.single.SingleSetSyncListener;
+import org.simantics.db.common.request.ResourceRead3;
+import org.simantics.db.common.request.WriteRequest;
+import org.simantics.db.common.utils.ListUtils;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.layer0.request.ActiveModels;
+import org.simantics.db.layer0.request.Model;
+import org.simantics.db.layer0.request.PossibleModel;
+import org.simantics.db.layer0.util.RemoverUtil;
+import org.simantics.db.layer0.variable.Variable;
+import org.simantics.db.service.VirtualGraphSupport;
+import org.simantics.issues.Severity;
+import org.simantics.issues.ontology.IssueResource;
+import org.simantics.layer0.Layer0;
+import org.simantics.operation.Layer0X;
+import org.simantics.scl.runtime.function.FunctionImpl2;
+import org.simantics.utils.datastructures.Pair;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public class IssueUtils {
+ private static final Logger LOGGER = LoggerFactory.getLogger(IssueUtils.class);
+
+ public static Resource toSeverityResource(IssueResource ISSUE, Severity severity) {
+ switch (severity) {
+ case ERROR: return ISSUE.Severity_Error;
+ case FATAL: return ISSUE.Severity_Fatal;
+ case INFO: return ISSUE.Severity_Info;
+ case WARNING: return ISSUE.Severity_Warning;
+ case NOTE: return ISSUE.Severity_Note;
+ default: return null;
+ }
+ }
+
+ public static Severity toSeverity(IssueResource ISSUE, Resource severity) {
+ if (severity == null)
+ return null;
+ if (severity.equals(ISSUE.Severity_Fatal))
+ return Severity.FATAL;
+ if (severity.equals(ISSUE.Severity_Error))
+ return Severity.ERROR;
+ if (severity.equals(ISSUE.Severity_Info))
+ return Severity.INFO;
+ if (severity.equals(ISSUE.Severity_Warning))
+ return Severity.WARNING;
+ if (severity.equals(ISSUE.Severity_Note))
+ return Severity.NOTE;
+ return null;
+ }
+
+ private static class IssueSourceDirtyListener extends FunctionImpl2<ReadGraph, List<Resource>, Boolean> {
+
+ private final IssueSource is;
+
+ public IssueSourceDirtyListener(IssueSource is) {
+ this.is = is;
+ }
+
+ @Override
+ public Boolean apply(ReadGraph graph, final List<Resource> resources) {
+ VirtualGraphSupport support = graph.getService(VirtualGraphSupport.class);
+ VirtualGraph vg = support.getWorkspacePersistent(IssueConstants.ISSUE_VG);
+ if (graph instanceof WriteGraph) {
+ try {
+ if(is.needUpdate(graph, resources)) {
+ graph.sync(new WriteRequest(vg) {
+ @Override
+ public void perform(WriteGraph graph) throws DatabaseException {
+ is.update(graph, resources);
+ }
+ });
+ }
+ } catch (DatabaseException e) {
+ LOGGER.error("Updating issue source failed.", e);
+ }
+ } else {
+ Session session = Simantics.getSession();
+ session.asyncRequest(new WriteRequest(vg) {
+ @Override
+ public void perform(WriteGraph graph) throws DatabaseException {
+ is.update(graph, resources);
+ }
+ });
+ }
+
+ return true;
+ }
+
+ }
+
+ private static class IssueSourceManagedIssuesListener extends SingleSetSyncListener<Resource> {
+
+ private final HashMap<Resource, IssueValidityListener> listeners = new HashMap<>();
+ private final AtomicBoolean disposed;
+ private final Resource source;
+ private final Resource model;
+
+ public IssueSourceManagedIssuesListener(AtomicBoolean disposed, Resource source, Resource model) {
+ this.disposed = disposed;
+ this.source = source;
+ this.model = model;
+ }
+
+ class IssueValidityListener extends DisposableSyncListener<Boolean> {
+
+ final private Resource issue;
+
+ public IssueValidityListener(Resource issue) {
+ this.issue = issue;
+ }
+
+ @Override
+ public void execute(ReadGraph graph, Boolean valid) throws DatabaseException {
+ if(!valid) {
+ VirtualGraphSupport support = graph.getService(VirtualGraphSupport.class);
+ VirtualGraph vg = support.getWorkspacePersistent(IssueConstants.ISSUE_VG);
+ graph.asyncRequest(new WriteRequest(vg) {
+
+ @Override
+ public void perform(WriteGraph graph) throws DatabaseException {
+ Issue desc = graph.sync(new StandardIssueDescription(issue));
+ if(desc == null)
+ return;
+ Resource context = (Resource)desc.getMainContext();
+ new DependencyIssueSynchronizer2(context, source).perform(graph);
+ }
+
+ });
+ }
+ }
+
+ @Override
+ public void exception(ReadGraph graph, Throwable throwable) throws DatabaseException {
+ LOGGER.error("IssueValidityListener received an exception.", throwable);
+ }
+
+ }
+
+ @Override
+ public void add(ReadGraph graph, final Resource issue) throws DatabaseException {
+ IssueValidityListener listener = new IssueValidityListener(issue);
+
+ graph.syncRequest(new ResourceRead3<Boolean>(issue, model, source) {
+
+ @Override
+ public Boolean perform(ReadGraph graph) throws DatabaseException {
+ Issue desc = graph.sync(new StandardIssueDescription(resource));
+ if(desc == null)
+ return false;
+ Resource context = (Resource)desc.getMainContext();
+ return graph.syncRequest(new DependencyIssueValidator2(context, resource2, resource3), TransientCacheListener.<Boolean>instance());
+ }
+
+ }, listener);
+
+ listeners.put(issue, listener);
+ }
+
+ @Override
+ public void remove(ReadGraph graph, final Resource issue) throws DatabaseException {
+ IssueValidityListener listener = listeners.remove(issue);
+ if(listener != null)
+ listener.dispose();
+ }
+
+ @Override
+ public void exception(ReadGraph graph, Throwable t) {
+ LOGGER.error("IssueSourceManagedIssuesListener received an exception.", t);
+ }
+
+ @Override
+ public boolean isDisposed() {
+ boolean disp = disposed.get();
+ if (disp) {
+ // Ensure validity listeners are cleared eventually.
+ if (!listeners.isEmpty()) {
+ for (IssueValidityListener listener : listeners.values()) {
+ listener.dispose();
+ }
+ listeners.clear();
+ }
+ }
+ return disp;
+ }
+
+ }
+
+ private static class ActiveIssueSourceListener extends SingleSetSyncListener<Resource> {
+
+ private final AtomicBoolean disposed;
+ private Map<Resource, Pair<IssueSource, IssueSourceDirtyListener>> sources = new HashMap<>();
+
+ public ActiveIssueSourceListener(AtomicBoolean disposed) {
+ this.disposed = disposed;
+ }
+
+ @Override
+ public void add(ReadGraph graph, final Resource source) throws DatabaseException {
+ IssueResource ISSUE = IssueResource.getInstance(graph);
+ boolean isListeningTracker = graph.isInstanceOf(source, ISSUE.Sources_ListeningDependencyTracker);
+ IssueSource is = graph.adapt(source, IssueSource.class);
+ final Resource model = isListeningTracker ? graph.syncRequest(new Model(source)) : null;
+
+ IssueSourceDirtyListener listener = new IssueSourceDirtyListener(is);
+ is.addDirtyListener(listener);
+ sources.put(source, Pair.make(is, listener));
+
+ if (isListeningTracker) {
+ graph.syncRequest(
+ new Objects(source, ISSUE.IssueSource_Manages),
+ new IssueSourceManagedIssuesListener(disposed, source, model));
+ }
+ }
+
+ @Override
+ public void remove(ReadGraph graph, final Resource source) throws DatabaseException {
+ Pair<IssueSource, IssueSourceDirtyListener> is = sources.remove(source);
+ if (is != null)
+ is.first.removeDirtyListener(is.second);
+ }
+
+ @Override
+ public void exception(ReadGraph graph, Throwable t) {
+ LOGGER.error("ActiveIssueSourceListener received an exception.", t);
+ }
+
+ @Override
+ public boolean isDisposed() {
+ return disposed.get();
+ }
+
+ }
+
+ public static Disposable listenActiveProjectIssueSources(RequestProcessor processor, Resource project) throws DatabaseException {
+ final AtomicBoolean disposed = new AtomicBoolean(false);
+ processor.syncRequest(
+ new ActiveProjectIssueSources(project, IssueResource.getInstance(processor).ContinuousIssueSource),
+ new ActiveIssueSourceListener(disposed));
+ return new Disposable() {
+ @Override
+ public void dispose() {
+ disposed.set(true);
+ }
+ };
+ }
+
+ public static List<Resource> getContextsForProperty(ReadGraph graph, Variable property) throws DatabaseException {
+ IssueResource ISSUE = IssueResource.getInstance(graph);
+ Variable issueVariable = property.getParent(graph);
+ Resource issueResource = issueVariable.getRepresents(graph);
+ Resource list = graph.getPossibleObject(issueResource, ISSUE.Issue_HasContexts);
+ if(list != null)
+ return ListUtils.toList(graph, list);
+ else
+ return Collections.emptyList();
+ }
+
+ public static void writeAdditionalContext(WriteGraph graph, Resource issue, List<Resource> contexts) throws DatabaseException {
+
+ if(contexts.isEmpty()) return;
+
+ IssueResource IR = IssueResource.getInstance(graph);
+
+ // The main context
+ graph.claim(issue, IR.Issue_HasContext, contexts.get(0));
+ // A possible parent
+ Layer0 L0 = Layer0.getInstance(graph);
+ Resource parent = graph.getPossibleObject(contexts.get(0), L0.PartOf);
+ if(parent != null) {
+ graph.claim(issue, IR.Issue_HasContext, parent);
+ }
+
+ }
+ public static void newUserIssue(WriteGraph graph, String label, Resource severity, List<Resource> contexts) throws DatabaseException {
+
+ Resource model = graph.sync(new PossibleModel(contexts.get(0)));
+ if(model == null) throw new DatabaseException("No model for main context");
+
+ newUserIssueForModel(graph, model, label, severity, contexts);
+
+ }
+
+ public static Resource newUserIssueForModel(WriteGraph graph) throws DatabaseException {
+
+ Resource project = Simantics.getProjectResource();
+ Collection<Resource> activeModels = graph.syncRequest(new ActiveModels(project));
+ if (activeModels.size() != 1)
+ return null;
+
+ IssueResource ISSUE = IssueResource.getInstance(graph);
+ Resource issue = null;
+ for (Resource model : activeModels) {
+ issue = newUserIssueForModel(graph, model, "New User Issue", ISSUE.Severity_Note, Collections.<Resource>emptyList());
+ }
+ return issue;
+
+ }
+
+
+ public static Resource newUserIssueForModel(WriteGraph graph, Resource model, String label, Resource severity, List<Resource> contexts) throws DatabaseException {
+ IssueResource ISSUE = IssueResource.getInstance(graph);
+ return newUserIssueForModel(graph, model, ISSUE.Issue, label, severity, contexts);
+ }
+
+ public static Resource newUserIssueForModel(WriteGraph graph, Resource model, Resource type, String label, Resource severity, List<Resource> contexts) throws DatabaseException {
+
+ Layer0 L0 = Layer0.getInstance(graph);
+ IssueResource ISSUE = IssueResource.getInstance(graph);
+
+ Resource issue = graph.newResource();
+ graph.claim(issue, ISSUE.UserIssue, ISSUE.UserIssue, issue);
+ graph.claim(issue, L0.InstanceOf, null, type);
+ graph.claim(issue, ISSUE.Issue_HasSeverity, null, severity);
+ graph.claim(issue, ISSUE.Issue_HasContexts, ListUtils.create(graph, L0.List, contexts));
+ writeAdditionalContext(graph, issue, contexts);
+ graph.claimLiteral(issue, L0.HasName, UUID.randomUUID().toString(), Bindings.STRING);
+ graph.claimLiteral(issue, L0.HasLabel, label, Bindings.STRING);
+ DateFormat format = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
+ Object time = format.format(new Date());
+ graph.claimLiteral(issue, ISSUE.Issue_creationTime, time, Bindings.STRING);
+ graph.claim(model, L0.ConsistsOf, L0.PartOf, issue);
+ return issue;
+
+ }
+
+ /**
+ * Creates a new issue based on SimpleIssue structure.
+ */
+ public static void newSimpleIssueForModel(WriteGraph graph, Resource model, Resource issueType, List<Resource> contexts, SimpleIssue simpleIssue) throws DatabaseException {
+
+ Layer0 L0 = Layer0.getInstance(graph);
+ IssueResource ISSUE = IssueResource.getInstance(graph);
+
+ Resource issue = graph.newResource();
+ graph.claim(issue, L0.InstanceOf, null, issueType);
+ graph.claim(issue, ISSUE.Issue_HasSeverity, null, toSeverityResource(ISSUE, simpleIssue.severity));
+ graph.claim(issue, ISSUE.Issue_HasContexts, ListUtils.create(graph, L0.List, contexts));
+ writeAdditionalContext(graph, issue, contexts);
+ graph.claimLiteral(issue, L0.HasName, UUID.randomUUID().toString(), Bindings.STRING);
+ graph.claimLiteral(issue, L0.HasLabel, simpleIssue.label, Bindings.STRING);
+ DateFormat format = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
+ Object time = format.format(new Date());
+ graph.claimLiteral(issue, ISSUE.Issue_creationTime, time, Bindings.STRING);
+ graph.claim(model, L0.ConsistsOf, L0.PartOf, issue);
+
+ }
+
+ /**
+ * Returns the set of all issues with given type.
+ */
+ public static Set<SimpleIssue> getSimpleIssues(ReadGraph graph, List<Resource> contexts, Resource issueType)
+ throws DatabaseException {
+ Layer0 L0 = Layer0.getInstance(graph);
+ IssueResource ISSUE = IssueResource.getInstance(graph);
+
+ THashSet<SimpleIssue> currentIssues = null;
+ for(Resource issue : graph.getObjects(contexts.get(0), ISSUE.Issue_HasContext_Inverse)) {
+ if(!graph.isInstanceOf(issue, issueType))
+ continue;
+ List<Resource> curContexts =
+ ListUtils.toList(graph, graph.getSingleObject(issue, ISSUE.Issue_HasContexts));
+ if(!contexts.equals(curContexts))
+ continue;
+ if(currentIssues == null)
+ currentIssues = new THashSet<SimpleIssue>();
+ currentIssues.add(new SimpleIssue(
+ (String)graph.getRelatedValue(issue, L0.HasLabel),
+ toSeverity(ISSUE, graph.getSingleObject(issue, ISSUE.Issue_HasSeverity)),
+ issue));
+ }
+ if(currentIssues == null)
+ return Collections.emptySet();
+ else
+ return currentIssues;
+ }
+
+ /**
+ * Creates and removes issues so that after the operation,
+ * the context has exactly the given issues with the given type.
+ */
+ public static void setSimpleIssues(WriteGraph graph, Resource model, List<Resource> contexts, Resource issueType, SimpleIssue ... issues) throws DatabaseException {
+ Set<SimpleIssue> currentIssues = getSimpleIssues(graph, contexts, issueType);
+ for(SimpleIssue newIssue : issues) {
+ if(currentIssues.contains(newIssue))
+ currentIssues.remove(newIssue);
+ else
+ newSimpleIssueForModel(graph, model, issueType, contexts, newIssue);
+ }
+ for(SimpleIssue oldIssue : currentIssues)
+ RemoverUtil.remove(graph, oldIssue.issueResource);
+ }
+
+ /**
+ * Creates and removes issues so that after the operation,
+ * the context has exactly the given issues with the given type.
+ */
+ public static void setSimpleIssues(WriteGraph graph, List<Resource> contexts, Resource issueType, SimpleIssue ... issues) throws DatabaseException {
+ Resource model = graph.sync(new PossibleModel(contexts.get(0)));
+ if(model == null) throw new DatabaseException("No model for main context");
+
+ setSimpleIssues(graph, model, contexts, issueType, issues);
+ }
+
+ /**
+ * Creates and removes issues so that after the operation,
+ * the context has exactly the given issues with the given type.
+ * Because this method is called in read transaction, it
+ * makes a write transaction if necessary and continues the
+ * operation there.
+ */
+ public static void setSimpleIssuesAsync(ReadGraph graph,
+ final List<Resource> contexts,
+ final Resource issueType,
+ final SimpleIssue ... issues) throws DatabaseException {
+ Resource model = graph.sync(new PossibleModel(contexts.get(0)));
+ if(model == null) throw new DatabaseException("No model for main context");
+
+ setSimpleIssuesAsync(graph, model, contexts, issueType, issues);
+ }
+
+ /**
+ * Creates and removes issues so that after the operation,
+ * the context has exactly the given issues with the given type.
+ * Because this method is called in read transaction, it
+ * makes a write transaction if necessary and continues the
+ * operation there.
+ */
+ public static void setSimpleIssuesAsync(ReadGraph graph,
+ final Resource model,
+ final List<Resource> contexts,
+ final Resource issueType,
+ final SimpleIssue ... issues) throws DatabaseException {
+ Set<SimpleIssue> oldIssues = getSimpleIssues(graph, contexts, issueType);
+
+ boolean needsUpdating = false;
+ if(issues.length != oldIssues.size())
+ needsUpdating = true;
+ else
+ for(SimpleIssue newIssue : issues) {
+ if(!oldIssues.contains(newIssue)) {
+ needsUpdating = true;
+ break;
+ }
+ }
+ if(needsUpdating) {
+ VirtualGraphSupport support = graph.getService(VirtualGraphSupport.class);
+ VirtualGraph vg = support.getWorkspacePersistent(IssueConstants.ISSUE_VG);
+ Simantics.getSession().asyncRequest(new WriteRequest(vg) {
+
+ @Override
+ public void perform(WriteGraph graph) throws DatabaseException {
+ setSimpleIssues(graph, model, contexts, issueType, issues);
+ }
+ });
+ }
+ }
+
+ public static Resource addIssueSource(WriteGraph g, Resource model, Resource sourceType, String name) throws DatabaseException {
+ Layer0 L0 = Layer0.getInstance(g);
+ Layer0X L0X = Layer0X.getInstance(g);
+
+ Resource source = g.newResource();
+ g.claim(source, L0.InstanceOf, null, sourceType);
+ g.addLiteral(source, L0.HasName, L0.NameOf, L0.String, name, Bindings.STRING);
+ g.claim(source, L0X.IsActivatedBy, L0X.Activates, model);
+ g.claim(source, L0.PartOf, L0.ConsistsOf, model);
+ return source;
+ }
+
+ public static String pathString(String uri, int startIndex) {
+ StringBuilder sb = new StringBuilder(uri.length() - startIndex + 1);
+ sb.append(URIStringUtils.NAMESPACE_PATH_SEPARATOR);
+ while (true) {
+ int nextSlash = uri.indexOf(URIStringUtils.NAMESPACE_PATH_SEPARATOR, startIndex);
+ if (nextSlash == -1) {
+ sb.append(URIStringUtils.unescape(uri.substring(startIndex, uri.length())));
+ break;
+ }
+ sb.append(URIStringUtils.unescape(uri.substring(startIndex, nextSlash))).append(URIStringUtils.NAMESPACE_PATH_SEPARATOR);
+ startIndex = nextSlash + 1;
+ }
+ return sb.toString();
+ }
+
+}