1 /*******************************************************************************
2 * Copyright (c) 2007, 2011 Association for Decentralized Information Management
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
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.issues.common;
14 import gnu.trove.set.hash.THashSet;
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;
25 import java.util.UUID;
26 import java.util.concurrent.atomic.AtomicBoolean;
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;
63 * @author Tuukka Lehtonen
65 public class IssueUtils {
66 private static final Logger LOGGER = LoggerFactory.getLogger(IssueUtils.class);
68 public static Resource toSeverityResource(IssueResource ISSUE, Severity 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;
79 public static Severity toSeverity(IssueResource ISSUE, Resource severity) {
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))
88 if (severity.equals(ISSUE.Severity_Warning))
89 return Severity.WARNING;
90 if (severity.equals(ISSUE.Severity_Note))
95 private static class IssueSourceDirtyListener extends FunctionImpl2<ReadGraph, List<Resource>, Boolean> {
97 private final IssueSource is;
99 public IssueSourceDirtyListener(IssueSource is) {
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) {
109 if(is.needUpdate(graph, resources)) {
110 graph.sync(new WriteRequest(vg) {
112 public void perform(WriteGraph graph) throws DatabaseException {
113 is.update(graph, resources);
117 } catch (DatabaseException e) {
118 LOGGER.error("Updating issue source failed.", e);
121 Session session = Simantics.getSession();
122 session.asyncRequest(new WriteRequest(vg) {
124 public void perform(WriteGraph graph) throws DatabaseException {
125 is.update(graph, resources);
135 private static class IssueSourceManagedIssuesListener extends SingleSetSyncListener<Resource> {
137 private final HashMap<Resource, IssueValidityListener> listeners = new HashMap<>();
138 private final AtomicBoolean disposed;
139 private final Resource source;
140 private final Resource model;
142 public IssueSourceManagedIssuesListener(AtomicBoolean disposed, Resource source, Resource model) {
143 this.disposed = disposed;
144 this.source = source;
148 class IssueValidityListener extends DisposableSyncListener<Boolean> {
150 final private Resource issue;
152 public IssueValidityListener(Resource issue) {
157 public void execute(ReadGraph graph, Boolean valid) throws DatabaseException {
159 VirtualGraphSupport support = graph.getService(VirtualGraphSupport.class);
160 VirtualGraph vg = support.getWorkspacePersistent(IssueConstants.ISSUE_VG);
161 graph.asyncRequest(new WriteRequest(vg) {
164 public void perform(WriteGraph graph) throws DatabaseException {
165 Issue desc = graph.sync(new StandardIssueDescription(issue));
168 Resource context = (Resource)desc.getMainContext();
169 new DependencyIssueSynchronizer2(context, source).perform(graph);
177 public void exception(ReadGraph graph, Throwable throwable) throws DatabaseException {
178 LOGGER.error("IssueValidityListener received an exception.", throwable);
184 public void add(ReadGraph graph, final Resource issue) throws DatabaseException {
185 IssueValidityListener listener = new IssueValidityListener(issue);
187 graph.syncRequest(new ResourceRead3<Boolean>(issue, model, source) {
190 public Boolean perform(ReadGraph graph) throws DatabaseException {
191 Issue desc = graph.sync(new StandardIssueDescription(resource));
194 Resource context = (Resource)desc.getMainContext();
195 return graph.syncRequest(new DependencyIssueValidator2(context, resource2, resource3), TransientCacheListener.<Boolean>instance());
200 listeners.put(issue, listener);
204 public void remove(ReadGraph graph, final Resource issue) throws DatabaseException {
205 IssueValidityListener listener = listeners.remove(issue);
211 public void exception(ReadGraph graph, Throwable t) {
212 LOGGER.error("IssueSourceManagedIssuesListener received an exception.", t);
216 public boolean isDisposed() {
217 boolean disp = disposed.get();
219 // Ensure validity listeners are cleared eventually.
220 if (!listeners.isEmpty()) {
221 for (IssueValidityListener listener : listeners.values()) {
232 private static class ActiveIssueSourceListener extends SingleSetSyncListener<Resource> {
234 private final AtomicBoolean disposed;
235 private Map<Resource, Pair<IssueSource, IssueSourceDirtyListener>> sources = new HashMap<>();
237 public ActiveIssueSourceListener(AtomicBoolean disposed) {
238 this.disposed = disposed;
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;
248 IssueSourceDirtyListener listener = new IssueSourceDirtyListener(is);
249 is.addDirtyListener(listener);
250 sources.put(source, Pair.make(is, listener));
252 if (isListeningTracker) {
254 new Objects(source, ISSUE.IssueSource_Manages),
255 new IssueSourceManagedIssuesListener(disposed, source, model));
260 public void remove(ReadGraph graph, final Resource source) throws DatabaseException {
261 Pair<IssueSource, IssueSourceDirtyListener> is = sources.remove(source);
263 is.first.removeDirtyListener(is.second);
267 public void exception(ReadGraph graph, Throwable t) {
268 LOGGER.error("ActiveIssueSourceListener received an exception.", t);
272 public boolean isDisposed() {
273 return disposed.get();
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() {
285 public void dispose() {
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);
297 return ListUtils.toList(graph, list);
299 return Collections.emptyList();
302 public static void writeAdditionalContext(WriteGraph graph, Resource issue, List<Resource> contexts) throws DatabaseException {
304 if(contexts.isEmpty()) return;
306 IssueResource IR = IssueResource.getInstance(graph);
309 graph.claim(issue, IR.Issue_HasContext, contexts.get(0));
311 Layer0 L0 = Layer0.getInstance(graph);
312 Resource parent = graph.getPossibleObject(contexts.get(0), L0.PartOf);
314 graph.claim(issue, IR.Issue_HasContext, parent);
318 public static void newUserIssue(WriteGraph graph, String label, Resource severity, List<Resource> contexts) throws DatabaseException {
320 Resource model = graph.sync(new PossibleModel(contexts.get(0)));
321 if(model == null) throw new DatabaseException("No model for main context");
323 newUserIssueForModel(graph, model, label, severity, contexts);
327 public static Resource newUserIssueForModel(WriteGraph graph) throws DatabaseException {
329 Resource project = Simantics.getProjectResource();
330 Collection<Resource> activeModels = graph.syncRequest(new ActiveModels(project));
331 if (activeModels.size() != 1)
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());
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);
349 public static Resource newUserIssueForModel(WriteGraph graph, Resource model, Resource type, String label, Resource severity, List<Resource> contexts) throws DatabaseException {
351 Layer0 L0 = Layer0.getInstance(graph);
352 IssueResource ISSUE = IssueResource.getInstance(graph);
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);
371 * Creates a new issue based on SimpleIssue structure.
373 public static void newSimpleIssueForModel(WriteGraph graph, Resource model, Resource issueType, List<Resource> contexts, SimpleIssue simpleIssue) throws DatabaseException {
375 Layer0 L0 = Layer0.getInstance(graph);
376 IssueResource ISSUE = IssueResource.getInstance(graph);
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);
393 * Returns the set of all issues with given type.
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);
400 THashSet<SimpleIssue> currentIssues = null;
401 for(Resource issue : graph.getObjects(contexts.get(0), ISSUE.Issue_HasContext_Inverse)) {
402 if(!graph.isInstanceOf(issue, issueType))
404 List<Resource> curContexts =
405 ListUtils.toList(graph, graph.getSingleObject(issue, ISSUE.Issue_HasContexts));
406 if(!contexts.equals(curContexts))
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)),
415 if(currentIssues == null)
416 return Collections.emptySet();
418 return currentIssues;
422 * Creates and removes issues so that after the operation,
423 * the context has exactly the given issues with the given type.
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);
431 newSimpleIssueForModel(graph, model, issueType, contexts, newIssue);
433 for(SimpleIssue oldIssue : currentIssues)
434 RemoverUtil.remove(graph, oldIssue.issueResource);
438 * Creates and removes issues so that after the operation,
439 * the context has exactly the given issues with the given type.
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");
445 setSimpleIssues(graph, model, contexts, issueType, issues);
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
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");
462 setSimpleIssuesAsync(graph, model, contexts, issueType, issues);
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
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);
479 boolean needsUpdating = false;
480 if(issues.length != oldIssues.size())
481 needsUpdating = true;
483 for(SimpleIssue newIssue : issues) {
484 if(!oldIssues.contains(newIssue)) {
485 needsUpdating = true;
490 VirtualGraphSupport support = graph.getService(VirtualGraphSupport.class);
491 VirtualGraph vg = support.getWorkspacePersistent(IssueConstants.ISSUE_VG);
492 Simantics.getSession().asyncRequest(new WriteRequest(vg) {
495 public void perform(WriteGraph graph) throws DatabaseException {
496 setSimpleIssues(graph, model, contexts, issueType, issues);
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);
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);
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);
518 int nextSlash = uri.indexOf(URIStringUtils.NAMESPACE_PATH_SEPARATOR, startIndex);
519 if (nextSlash == -1) {
520 sb.append(URIStringUtils.unescape(uri.substring(startIndex, uri.length())));
523 sb.append(URIStringUtils.unescape(uri.substring(startIndex, nextSlash))).append(URIStringUtils.NAMESPACE_PATH_SEPARATOR);
524 startIndex = nextSlash + 1;
526 return sb.toString();