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
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.issues.common;
\r
14 import gnu.trove.set.hash.THashSet;
\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
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
62 * @author Tuukka Lehtonen
\r
64 public class IssueUtils {
\r
66 public static Resource toSeverityResource(IssueResource ISSUE, Severity 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
77 public static Severity toSeverity(IssueResource ISSUE, Resource severity) {
\r
78 if (severity == 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
93 private static class IssueSourceDirtyListener extends FunctionImpl2<ReadGraph, List<Resource>, Boolean> {
\r
95 private final IssueSource is;
\r
97 public IssueSourceDirtyListener(IssueSource is) {
\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
107 if(is.needUpdate(graph, resources)) {
\r
108 graph.sync(new WriteRequest(vg) {
\r
110 public void perform(WriteGraph graph) throws DatabaseException {
\r
111 is.update(graph, resources);
\r
115 } catch (DatabaseException e) {
\r
116 Logger.defaultLogError(e);
\r
119 Session session = Simantics.getSession();
\r
120 session.asyncRequest(new WriteRequest(vg) {
\r
122 public void perform(WriteGraph graph) throws DatabaseException {
\r
123 is.update(graph, resources);
\r
133 private static class IssueSourceManagedIssuesListener extends SingleSetSyncListener<Resource> {
\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
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
146 class IssueValidityListener extends DisposableSyncListener<Boolean> {
\r
148 final private Resource issue;
\r
150 public IssueValidityListener(Resource issue) {
\r
151 this.issue = issue;
\r
155 public void execute(ReadGraph graph, Boolean valid) throws DatabaseException {
\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
162 public void perform(WriteGraph graph) throws DatabaseException {
\r
163 Issue desc = graph.sync(new StandardIssueDescription(issue));
\r
166 Resource context = (Resource)desc.getMainContext();
\r
167 new DependencyIssueSynchronizer2(context, source).perform(graph);
\r
175 public void exception(ReadGraph graph, Throwable throwable) throws DatabaseException {
\r
176 Logger.defaultLogError(throwable);
\r
182 public void add(ReadGraph graph, final Resource issue) throws DatabaseException {
\r
183 IssueValidityListener listener = new IssueValidityListener(issue);
\r
185 graph.asyncRequest(new ResourceRead3<Boolean>(issue, model, source) {
\r
188 public Boolean perform(ReadGraph graph) throws DatabaseException {
\r
189 Issue desc = graph.sync(new StandardIssueDescription(resource));
\r
192 Resource context = (Resource)desc.getMainContext();
\r
193 return graph.syncRequest(new DependencyIssueValidator2(context, resource2, resource3), TransientCacheListener.<Boolean>instance());
\r
198 listeners.put(issue, listener);
\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
209 public void exception(ReadGraph graph, Throwable t) {
\r
210 Logger.defaultLogError(t);
\r
214 public boolean isDisposed() {
\r
215 boolean disp = disposed.get();
\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
230 private static class ActiveIssueSourceListener extends SingleSetSyncListener<Resource> {
\r
232 private final AtomicBoolean disposed;
\r
233 private Map<Resource, Pair<IssueSource, IssueSourceDirtyListener>> sources = new HashMap<>();
\r
235 public ActiveIssueSourceListener(AtomicBoolean disposed) {
\r
236 this.disposed = disposed;
\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
246 IssueSourceDirtyListener listener = new IssueSourceDirtyListener(is);
\r
247 is.addDirtyListener(listener);
\r
248 sources.put(source, Pair.make(is, listener));
\r
250 if (isListeningTracker) {
\r
251 graph.asyncRequest(
\r
252 new Objects(source, ISSUE.IssueSource_Manages),
\r
253 new IssueSourceManagedIssuesListener(disposed, source, model));
\r
258 public void remove(ReadGraph graph, final Resource source) throws DatabaseException {
\r
259 Pair<IssueSource, IssueSourceDirtyListener> is = sources.remove(source);
\r
261 is.first.removeDirtyListener(is.second);
\r
265 public void exception(ReadGraph graph, Throwable t) {
\r
266 Logger.defaultLogError(t);
\r
270 public boolean isDisposed() {
\r
271 return disposed.get();
\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
283 public void dispose() {
\r
284 disposed.set(true);
\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
295 return ListUtils.toList(graph, list);
\r
297 return Collections.emptyList();
\r
300 public static void writeAdditionalContext(WriteGraph graph, Resource issue, List<Resource> contexts) throws DatabaseException {
\r
302 if(contexts.isEmpty()) return;
\r
304 IssueResource IR = IssueResource.getInstance(graph);
\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
316 public static void newUserIssue(WriteGraph graph, String label, Resource severity, List<Resource> contexts) throws DatabaseException {
\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
321 newUserIssueForModel(graph, model, label, severity, contexts);
\r
325 public static Resource newUserIssueForModel(WriteGraph graph) throws DatabaseException {
\r
327 Resource project = Simantics.getProjectResource();
\r
328 Collection<Resource> activeModels = graph.syncRequest(new ActiveModels(project));
\r
329 if (activeModels.size() != 1)
\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
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
347 public static Resource newUserIssueForModel(WriteGraph graph, Resource model, Resource type, String label, Resource severity, List<Resource> contexts) throws DatabaseException {
\r
349 Layer0 L0 = Layer0.getInstance(graph);
\r
350 IssueResource ISSUE = IssueResource.getInstance(graph);
\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
369 * Creates a new issue based on SimpleIssue structure.
\r
371 public static void newSimpleIssueForModel(WriteGraph graph, Resource model, Resource issueType, List<Resource> contexts, SimpleIssue simpleIssue) throws DatabaseException {
\r
373 Layer0 L0 = Layer0.getInstance(graph);
\r
374 IssueResource ISSUE = IssueResource.getInstance(graph);
\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
391 * Returns the set of all issues with given type.
\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
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
402 List<Resource> curContexts =
\r
403 ListUtils.toList(graph, graph.getSingleObject(issue, ISSUE.Issue_HasContexts));
\r
404 if(!contexts.equals(curContexts))
\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
413 if(currentIssues == null)
\r
414 return Collections.emptySet();
\r
416 return currentIssues;
\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
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
429 newSimpleIssueForModel(graph, model, issueType, contexts, newIssue);
\r
431 for(SimpleIssue oldIssue : currentIssues)
\r
432 RemoverUtil.remove(graph, oldIssue.issueResource);
\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
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
443 setSimpleIssues(graph, model, contexts, issueType, issues);
\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
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
460 setSimpleIssuesAsync(graph, model, contexts, issueType, issues);
\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
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
477 boolean needsUpdating = false;
\r
478 if(issues.length != oldIssues.size())
\r
479 needsUpdating = true;
\r
481 for(SimpleIssue newIssue : issues) {
\r
482 if(!oldIssues.contains(newIssue)) {
\r
483 needsUpdating = true;
\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
493 public void perform(WriteGraph graph) throws DatabaseException {
\r
494 setSimpleIssues(graph, model, contexts, issueType, issues);
\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
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
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
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
521 sb.append(URIStringUtils.unescape(uri.substring(startIndex, nextSlash))).append(URIStringUtils.NAMESPACE_PATH_SEPARATOR);
\r
522 startIndex = nextSlash + 1;
\r
524 return sb.toString();
\r