]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.issues.common/src/org/simantics/issues/common/IssueUtils.java
Merge commit 'bf75fd9'
[simantics/platform.git] / bundles / org.simantics.issues.common / src / org / simantics / issues / common / IssueUtils.java
1 /*******************************************************************************\r
2  * Copyright (c) 2007, 2011 Association for Decentralized Information Management\r
3  * in Industry THTH ry.\r
4  * All rights reserved. This program and the accompanying materials\r
5  * are made available under the terms of the Eclipse Public License v1.0\r
6  * which accompanies this distribution, and is available at\r
7  * http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.issues.common;\r
13 \r
14 import gnu.trove.set.hash.THashSet;\r
15 \r
16 import java.text.DateFormat;\r
17 import java.text.SimpleDateFormat;\r
18 import java.util.Collection;\r
19 import java.util.Collections;\r
20 import java.util.Date;\r
21 import java.util.HashMap;\r
22 import java.util.List;\r
23 import java.util.Map;\r
24 import java.util.Set;\r
25 import java.util.UUID;\r
26 import java.util.concurrent.atomic.AtomicBoolean;\r
27 \r
28 import org.simantics.Simantics;\r
29 import org.simantics.databoard.Bindings;\r
30 import org.simantics.databoard.util.URIStringUtils;\r
31 import org.simantics.db.Disposable;\r
32 import org.simantics.db.Issue;\r
33 import org.simantics.db.ReadGraph;\r
34 import org.simantics.db.RequestProcessor;\r
35 import org.simantics.db.Resource;\r
36 import org.simantics.db.Session;\r
37 import org.simantics.db.VirtualGraph;\r
38 import org.simantics.db.WriteGraph;\r
39 import org.simantics.db.common.primitiverequest.Objects;\r
40 import org.simantics.db.common.procedure.adapter.DisposableSyncListener;\r
41 import org.simantics.db.common.procedure.adapter.TransientCacheListener;\r
42 import org.simantics.db.common.procedure.single.SingleSetSyncListener;\r
43 import org.simantics.db.common.request.ResourceRead3;\r
44 import org.simantics.db.common.request.WriteRequest;\r
45 import org.simantics.db.common.utils.ListUtils;\r
46 import org.simantics.db.exception.DatabaseException;\r
47 import org.simantics.db.layer0.request.ActiveModels;\r
48 import org.simantics.db.layer0.request.Model;\r
49 import org.simantics.db.layer0.request.PossibleModel;\r
50 import org.simantics.db.layer0.util.RemoverUtil;\r
51 import org.simantics.db.layer0.variable.Variable;\r
52 import org.simantics.db.service.VirtualGraphSupport;\r
53 import org.simantics.issues.Severity;\r
54 import org.simantics.issues.ontology.IssueResource;\r
55 import org.simantics.layer0.Layer0;\r
56 import org.simantics.operation.Layer0X;\r
57 import org.simantics.scl.runtime.function.FunctionImpl2;\r
58 import org.simantics.utils.datastructures.Pair;\r
59 import org.slf4j.Logger;\r
60 import org.slf4j.LoggerFactory;\r
61 \r
62 /**\r
63  * @author Tuukka Lehtonen\r
64  */\r
65 public class IssueUtils {\r
66     private static final Logger LOGGER = LoggerFactory.getLogger(IssueUtils.class);\r
67 \r
68     public static Resource toSeverityResource(IssueResource ISSUE, Severity severity) {\r
69         switch (severity) {\r
70             case ERROR: return ISSUE.Severity_Error;\r
71             case FATAL: return ISSUE.Severity_Fatal;\r
72             case INFO: return ISSUE.Severity_Info;\r
73             case WARNING: return ISSUE.Severity_Warning;\r
74             case NOTE: return ISSUE.Severity_Note;\r
75             default: return null;\r
76         }\r
77     }\r
78 \r
79     public static Severity toSeverity(IssueResource ISSUE, Resource severity) {\r
80         if (severity == null)\r
81             return null;\r
82         if (severity.equals(ISSUE.Severity_Fatal))\r
83             return Severity.FATAL;\r
84         if (severity.equals(ISSUE.Severity_Error))\r
85             return Severity.ERROR;\r
86         if (severity.equals(ISSUE.Severity_Info))\r
87             return Severity.INFO;\r
88         if (severity.equals(ISSUE.Severity_Warning))\r
89             return Severity.WARNING;\r
90         if (severity.equals(ISSUE.Severity_Note))\r
91             return Severity.NOTE;\r
92         return null;\r
93     }\r
94 \r
95     private static class IssueSourceDirtyListener extends FunctionImpl2<ReadGraph, List<Resource>, Boolean> {\r
96 \r
97         private final IssueSource is;\r
98 \r
99         public IssueSourceDirtyListener(IssueSource is) {\r
100             this.is = is;\r
101         }\r
102 \r
103         @Override\r
104         public Boolean apply(ReadGraph graph, final List<Resource> resources) {\r
105             VirtualGraphSupport support = graph.getService(VirtualGraphSupport.class);\r
106             VirtualGraph vg = support.getWorkspacePersistent(IssueConstants.ISSUE_VG);\r
107             if (graph instanceof WriteGraph) {\r
108                 try {\r
109                     if(is.needUpdate(graph, resources)) {\r
110                         graph.sync(new WriteRequest(vg) {\r
111                             @Override\r
112                             public void perform(WriteGraph graph) throws DatabaseException {\r
113                                 is.update(graph, resources);\r
114                             }\r
115                         });\r
116                     }\r
117                 } catch (DatabaseException e) {\r
118                     LOGGER.error("Updating issue source failed.", e);\r
119                 }\r
120             } else {\r
121                 Session session = Simantics.getSession();\r
122                 session.asyncRequest(new WriteRequest(vg) {\r
123                     @Override\r
124                     public void perform(WriteGraph graph) throws DatabaseException {\r
125                         is.update(graph, resources);\r
126                     }\r
127                 });\r
128             }\r
129 \r
130             return true;\r
131         }\r
132 \r
133     }\r
134 \r
135     private static class IssueSourceManagedIssuesListener extends SingleSetSyncListener<Resource> {\r
136 \r
137         private final HashMap<Resource, IssueValidityListener> listeners = new HashMap<>();\r
138         private final AtomicBoolean disposed;\r
139         private final Resource source;\r
140         private final Resource model;\r
141 \r
142         public IssueSourceManagedIssuesListener(AtomicBoolean disposed, Resource source, Resource model) {\r
143             this.disposed = disposed;\r
144             this.source = source;\r
145             this.model = model;\r
146         }\r
147 \r
148         class IssueValidityListener extends DisposableSyncListener<Boolean> {\r
149 \r
150             final private Resource issue;\r
151 \r
152             public IssueValidityListener(Resource issue) {\r
153                 this.issue = issue;\r
154             }\r
155 \r
156             @Override\r
157             public void execute(ReadGraph graph, Boolean valid) throws DatabaseException {\r
158                 if(!valid) {\r
159                     VirtualGraphSupport support = graph.getService(VirtualGraphSupport.class);\r
160                     VirtualGraph vg = support.getWorkspacePersistent(IssueConstants.ISSUE_VG);\r
161                     graph.asyncRequest(new WriteRequest(vg) {\r
162 \r
163                         @Override\r
164                         public void perform(WriteGraph graph) throws DatabaseException {\r
165                             Issue desc = graph.sync(new StandardIssueDescription(issue));\r
166                             if(desc == null)\r
167                                 return;\r
168                             Resource context = (Resource)desc.getMainContext();\r
169                             new DependencyIssueSynchronizer2(context, source).perform(graph);\r
170                         }\r
171                         \r
172                     });\r
173                 }\r
174             }\r
175 \r
176             @Override\r
177             public void exception(ReadGraph graph, Throwable throwable) throws DatabaseException {\r
178                 LOGGER.error("IssueValidityListener received an exception.", throwable);\r
179             }\r
180 \r
181         }\r
182 \r
183         @Override\r
184         public void add(ReadGraph graph, final Resource issue) throws DatabaseException {\r
185             IssueValidityListener listener = new IssueValidityListener(issue);\r
186 \r
187             graph.asyncRequest(new ResourceRead3<Boolean>(issue, model, source) {\r
188 \r
189                 @Override\r
190                 public Boolean perform(ReadGraph graph) throws DatabaseException {\r
191                     Issue desc = graph.sync(new StandardIssueDescription(resource));\r
192                     if(desc == null)\r
193                         return false;\r
194                     Resource context = (Resource)desc.getMainContext();\r
195                     return graph.syncRequest(new DependencyIssueValidator2(context, resource2, resource3), TransientCacheListener.<Boolean>instance());\r
196                 }\r
197 \r
198             }, listener);\r
199 \r
200             listeners.put(issue, listener);\r
201         }\r
202 \r
203         @Override\r
204         public void remove(ReadGraph graph, final Resource issue) throws DatabaseException {\r
205             IssueValidityListener listener = listeners.remove(issue);\r
206             if(listener != null)\r
207                 listener.dispose();\r
208         }\r
209 \r
210         @Override\r
211         public void exception(ReadGraph graph, Throwable t) {\r
212             LOGGER.error("IssueSourceManagedIssuesListener received an exception.", t);\r
213         }\r
214 \r
215         @Override\r
216         public boolean isDisposed() {\r
217             boolean disp = disposed.get();\r
218             if (disp) {\r
219                 // Ensure validity listeners are cleared eventually.\r
220                 if (!listeners.isEmpty()) {\r
221                     for (IssueValidityListener listener : listeners.values()) {\r
222                         listener.dispose();\r
223                     }\r
224                     listeners.clear();\r
225                 }\r
226             }\r
227             return disp;\r
228         }\r
229 \r
230     }\r
231 \r
232     private static class ActiveIssueSourceListener extends SingleSetSyncListener<Resource> {\r
233 \r
234         private final AtomicBoolean disposed;\r
235         private Map<Resource, Pair<IssueSource, IssueSourceDirtyListener>> sources = new HashMap<>();\r
236 \r
237         public ActiveIssueSourceListener(AtomicBoolean disposed) {\r
238             this.disposed = disposed;\r
239         }\r
240 \r
241         @Override\r
242         public void add(ReadGraph graph, final Resource source) throws DatabaseException {\r
243             IssueResource ISSUE = IssueResource.getInstance(graph);\r
244             boolean isListeningTracker = graph.isInstanceOf(source, ISSUE.Sources_ListeningDependencyTracker);\r
245             IssueSource is = graph.adapt(source, IssueSource.class);\r
246             final Resource model = isListeningTracker ? graph.syncRequest(new Model(source)) : null;\r
247 \r
248             IssueSourceDirtyListener listener = new IssueSourceDirtyListener(is);\r
249             is.addDirtyListener(listener);\r
250             sources.put(source, Pair.make(is, listener));\r
251 \r
252             if (isListeningTracker) {\r
253                 graph.asyncRequest(\r
254                         new Objects(source, ISSUE.IssueSource_Manages),\r
255                         new IssueSourceManagedIssuesListener(disposed, source, model));\r
256             }\r
257         }\r
258 \r
259         @Override\r
260         public void remove(ReadGraph graph, final Resource source) throws DatabaseException {\r
261             Pair<IssueSource, IssueSourceDirtyListener> is = sources.remove(source);\r
262             if (is != null)\r
263                 is.first.removeDirtyListener(is.second);\r
264         }\r
265 \r
266         @Override\r
267         public void exception(ReadGraph graph, Throwable t) {\r
268             LOGGER.error("ActiveIssueSourceListener received an exception.", t);\r
269         }\r
270 \r
271         @Override\r
272         public boolean isDisposed() {\r
273             return disposed.get();\r
274         }\r
275 \r
276     }\r
277 \r
278     public static Disposable listenActiveProjectIssueSources(RequestProcessor processor, Resource project) throws DatabaseException {\r
279         final AtomicBoolean disposed = new AtomicBoolean(false);\r
280         processor.syncRequest(\r
281                 new ActiveProjectIssueSources(project),\r
282                 new ActiveIssueSourceListener(disposed));\r
283         return new Disposable() {\r
284             @Override\r
285             public void dispose() {\r
286                 disposed.set(true);\r
287             }\r
288         };\r
289     }\r
290 \r
291     public static List<Resource> getContextsForProperty(ReadGraph graph, Variable property) throws DatabaseException {\r
292         IssueResource ISSUE = IssueResource.getInstance(graph);\r
293         Variable issueVariable = property.getParent(graph);\r
294         Resource issueResource = issueVariable.getRepresents(graph);\r
295         Resource list = graph.getPossibleObject(issueResource, ISSUE.Issue_HasContexts);\r
296         if(list != null)\r
297                 return ListUtils.toList(graph, list);\r
298         else\r
299                 return Collections.emptyList();\r
300     }\r
301     \r
302         public static void writeAdditionalContext(WriteGraph graph, Resource issue, List<Resource> contexts) throws DatabaseException {\r
303 \r
304                 if(contexts.isEmpty()) return;\r
305                 \r
306                 IssueResource IR = IssueResource.getInstance(graph);\r
307 \r
308                 // The main context\r
309         graph.claim(issue, IR.Issue_HasContext, contexts.get(0));\r
310         // A possible parent\r
311                 Layer0 L0 = Layer0.getInstance(graph);\r
312                 Resource parent = graph.getPossibleObject(contexts.get(0), L0.PartOf);\r
313         if(parent != null) {\r
314                 graph.claim(issue, IR.Issue_HasContext, parent);\r
315         }\r
316                 \r
317         }    \r
318     public static void newUserIssue(WriteGraph graph, String label, Resource severity, List<Resource> contexts) throws DatabaseException {\r
319         \r
320                 Resource model = graph.sync(new PossibleModel(contexts.get(0)));\r
321                 if(model == null) throw new DatabaseException("No model for main context");\r
322                 \r
323                 newUserIssueForModel(graph, model, label, severity, contexts);\r
324                 \r
325     }\r
326     \r
327     public static Resource newUserIssueForModel(WriteGraph graph) throws DatabaseException {\r
328         \r
329         Resource project = Simantics.getProjectResource();\r
330         Collection<Resource> activeModels = graph.syncRequest(new ActiveModels(project));\r
331         if (activeModels.size() != 1)\r
332             return null;\r
333 \r
334         IssueResource ISSUE = IssueResource.getInstance(graph);\r
335         Resource issue = null;\r
336         for (Resource model : activeModels) {\r
337             issue = newUserIssueForModel(graph, model, "New User Issue", ISSUE.Severity_Note, Collections.<Resource>emptyList());\r
338         }\r
339                 return issue;\r
340         \r
341     }\r
342      \r
343 \r
344     public static Resource newUserIssueForModel(WriteGraph graph, Resource model, String label, Resource severity, List<Resource> contexts) throws DatabaseException {\r
345         IssueResource ISSUE = IssueResource.getInstance(graph);\r
346         return newUserIssueForModel(graph, model, ISSUE.Issue, label, severity, contexts);\r
347     }\r
348 \r
349     public static Resource newUserIssueForModel(WriteGraph graph, Resource model, Resource type, String label, Resource severity, List<Resource> contexts) throws DatabaseException {\r
350 \r
351         Layer0 L0 = Layer0.getInstance(graph);\r
352         IssueResource ISSUE = IssueResource.getInstance(graph);\r
353         \r
354         Resource issue = graph.newResource();\r
355                 graph.claim(issue, ISSUE.UserIssue, ISSUE.UserIssue, issue);\r
356                 graph.claim(issue, L0.InstanceOf, null, type);\r
357                 graph.claim(issue, ISSUE.Issue_HasSeverity, null, severity);\r
358                 graph.claim(issue, ISSUE.Issue_HasContexts, ListUtils.create(graph, L0.List, contexts));\r
359                 writeAdditionalContext(graph, issue, contexts);\r
360                 graph.claimLiteral(issue, L0.HasName, UUID.randomUUID().toString(), Bindings.STRING);\r
361                 graph.claimLiteral(issue, L0.HasLabel, label, Bindings.STRING);\r
362                 DateFormat format = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");\r
363                 Object time = format.format(new Date());\r
364                 graph.claimLiteral(issue, ISSUE.Issue_creationTime, time, Bindings.STRING);\r
365                 graph.claim(model, L0.ConsistsOf, L0.PartOf, issue);\r
366                 return issue;\r
367         \r
368     }\r
369     \r
370     /**\r
371      * Creates a new issue based on SimpleIssue structure.\r
372      */\r
373     public static void newSimpleIssueForModel(WriteGraph graph, Resource model, Resource issueType, List<Resource> contexts, SimpleIssue simpleIssue) throws DatabaseException {\r
374 \r
375         Layer0 L0 = Layer0.getInstance(graph);\r
376         IssueResource ISSUE = IssueResource.getInstance(graph);\r
377         \r
378         Resource issue = graph.newResource();\r
379         graph.claim(issue, L0.InstanceOf, null, issueType);\r
380         graph.claim(issue, ISSUE.Issue_HasSeverity, null, toSeverityResource(ISSUE, simpleIssue.severity));\r
381         graph.claim(issue, ISSUE.Issue_HasContexts, ListUtils.create(graph, L0.List, contexts));\r
382         writeAdditionalContext(graph, issue, contexts);\r
383         graph.claimLiteral(issue, L0.HasName, UUID.randomUUID().toString(), Bindings.STRING);\r
384         graph.claimLiteral(issue, L0.HasLabel, simpleIssue.label, Bindings.STRING);\r
385         DateFormat format = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");\r
386         Object time = format.format(new Date());\r
387         graph.claimLiteral(issue, ISSUE.Issue_creationTime, time, Bindings.STRING);\r
388         graph.claim(model, L0.ConsistsOf, L0.PartOf, issue);\r
389         \r
390     }\r
391     \r
392     /**\r
393      * Returns the set of all issues with given type.\r
394      */\r
395     public static Set<SimpleIssue> getSimpleIssues(ReadGraph graph, List<Resource> contexts, Resource issueType)\r
396             throws DatabaseException {\r
397         Layer0 L0 = Layer0.getInstance(graph);\r
398         IssueResource ISSUE = IssueResource.getInstance(graph);\r
399         \r
400         THashSet<SimpleIssue> currentIssues = null;\r
401         for(Resource issue : graph.getObjects(contexts.get(0), ISSUE.Issue_HasContext_Inverse)) {\r
402             if(!graph.isInstanceOf(issue, issueType))\r
403                 continue;\r
404             List<Resource> curContexts = \r
405                     ListUtils.toList(graph, graph.getSingleObject(issue, ISSUE.Issue_HasContexts));\r
406             if(!contexts.equals(curContexts))\r
407                 continue;\r
408             if(currentIssues == null)\r
409                 currentIssues = new THashSet<SimpleIssue>();\r
410             currentIssues.add(new SimpleIssue(\r
411                     (String)graph.getRelatedValue(issue, L0.HasLabel),\r
412                     toSeverity(ISSUE, graph.getSingleObject(issue, ISSUE.Issue_HasSeverity)), \r
413                     issue));\r
414         }\r
415         if(currentIssues == null)\r
416             return Collections.emptySet();\r
417         else\r
418             return currentIssues;\r
419     }\r
420     \r
421     /**\r
422      * Creates and removes issues so that after the operation, \r
423      * the context has exactly the given issues with the given type.\r
424      */\r
425     public static void setSimpleIssues(WriteGraph graph, Resource model, List<Resource> contexts, Resource issueType, SimpleIssue ... issues) throws DatabaseException {\r
426         Set<SimpleIssue> currentIssues = getSimpleIssues(graph, contexts, issueType);\r
427         for(SimpleIssue newIssue : issues) {\r
428             if(currentIssues.contains(newIssue))\r
429                 currentIssues.remove(newIssue);\r
430             else\r
431                 newSimpleIssueForModel(graph, model, issueType, contexts, newIssue);\r
432         }\r
433         for(SimpleIssue oldIssue : currentIssues)\r
434             RemoverUtil.remove(graph, oldIssue.issueResource);\r
435     }\r
436     \r
437     /**\r
438      * Creates and removes issues so that after the operation, \r
439      * the context has exactly the given issues with the given type.\r
440      */\r
441     public static void setSimpleIssues(WriteGraph graph, List<Resource> contexts, Resource issueType, SimpleIssue ... issues) throws DatabaseException {\r
442         Resource model = graph.sync(new PossibleModel(contexts.get(0)));\r
443         if(model == null) throw new DatabaseException("No model for main context");\r
444      \r
445         setSimpleIssues(graph, model, contexts, issueType, issues);\r
446     }\r
447     \r
448     /**\r
449      * Creates and removes issues so that after the operation, \r
450      * the context has exactly the given issues with the given type.\r
451      * Because this method is called in read transaction, it \r
452      * makes a write transaction if necessary and continues the\r
453      * operation there.\r
454      */\r
455     public static void setSimpleIssuesAsync(ReadGraph graph,\r
456             final List<Resource> contexts,\r
457             final Resource issueType, \r
458             final SimpleIssue ... issues) throws DatabaseException {\r
459         Resource model = graph.sync(new PossibleModel(contexts.get(0)));\r
460         if(model == null) throw new DatabaseException("No model for main context");\r
461      \r
462         setSimpleIssuesAsync(graph, model, contexts, issueType, issues);\r
463     }\r
464     \r
465     /**\r
466      * Creates and removes issues so that after the operation, \r
467      * the context has exactly the given issues with the given type.\r
468      * Because this method is called in read transaction, it \r
469      * makes a write transaction if necessary and continues the\r
470      * operation there.\r
471      */\r
472     public static void setSimpleIssuesAsync(ReadGraph graph, \r
473             final Resource model, \r
474             final List<Resource> contexts,\r
475             final Resource issueType, \r
476             final SimpleIssue ... issues) throws DatabaseException {\r
477         Set<SimpleIssue> oldIssues = getSimpleIssues(graph, contexts, issueType);\r
478         \r
479         boolean needsUpdating = false;\r
480         if(issues.length != oldIssues.size())\r
481             needsUpdating = true;\r
482         else \r
483             for(SimpleIssue newIssue : issues) {\r
484                 if(!oldIssues.contains(newIssue)) {\r
485                     needsUpdating = true;\r
486                     break;\r
487                 }\r
488             }\r
489         if(needsUpdating) {\r
490             VirtualGraphSupport support = graph.getService(VirtualGraphSupport.class);\r
491             VirtualGraph vg = support.getWorkspacePersistent(IssueConstants.ISSUE_VG);\r
492             Simantics.getSession().asyncRequest(new WriteRequest(vg) {\r
493                 \r
494                 @Override\r
495                 public void perform(WriteGraph graph) throws DatabaseException {\r
496                     setSimpleIssues(graph, model, contexts, issueType, issues);\r
497                 }\r
498             });\r
499         }\r
500     }\r
501     \r
502         public static Resource addIssueSource(WriteGraph g, Resource model, Resource sourceType, String name) throws DatabaseException {\r
503         Layer0 L0 = Layer0.getInstance(g);\r
504         Layer0X L0X = Layer0X.getInstance(g);\r
505 \r
506         Resource source = g.newResource();\r
507         g.claim(source, L0.InstanceOf, null, sourceType);\r
508         g.addLiteral(source, L0.HasName, L0.NameOf, L0.String, name, Bindings.STRING);\r
509         g.claim(source, L0X.IsActivatedBy, L0X.Activates, model);\r
510         g.claim(source, L0.PartOf, L0.ConsistsOf, model);\r
511         return source;\r
512     }\r
513 \r
514     public static String pathString(String uri, int startIndex) {\r
515         StringBuilder sb = new StringBuilder(uri.length() - startIndex + 1);\r
516         sb.append(URIStringUtils.NAMESPACE_PATH_SEPARATOR);\r
517         while (true) {\r
518             int nextSlash = uri.indexOf(URIStringUtils.NAMESPACE_PATH_SEPARATOR, startIndex);\r
519             if (nextSlash == -1) {\r
520                 sb.append(URIStringUtils.unescape(uri.substring(startIndex, uri.length())));\r
521                 break;\r
522             }\r
523             sb.append(URIStringUtils.unescape(uri.substring(startIndex, nextSlash))).append(URIStringUtils.NAMESPACE_PATH_SEPARATOR);\r
524             startIndex = nextSlash + 1;\r
525         }\r
526         return sb.toString();\r
527     }\r
528 \r
529 }\r