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