--- /dev/null
+/*******************************************************************************\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.Logger;\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
+\r
+/**\r
+ * @author Tuukka Lehtonen\r
+ */\r
+public class IssueUtils {\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.defaultLogError(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.defaultLogError(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.defaultLogError(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.defaultLogError(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