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