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.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
63 * @author Tuukka Lehtonen
\r
65 public class IssueUtils {
\r
66 private static final Logger LOGGER = LoggerFactory.getLogger(IssueUtils.class);
\r
68 public static Resource toSeverityResource(IssueResource ISSUE, Severity 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
79 public static Severity toSeverity(IssueResource ISSUE, Resource severity) {
\r
80 if (severity == 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
95 private static class IssueSourceDirtyListener extends FunctionImpl2<ReadGraph, List<Resource>, Boolean> {
\r
97 private final IssueSource is;
\r
99 public IssueSourceDirtyListener(IssueSource is) {
\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
109 if(is.needUpdate(graph, resources)) {
\r
110 graph.sync(new WriteRequest(vg) {
\r
112 public void perform(WriteGraph graph) throws DatabaseException {
\r
113 is.update(graph, resources);
\r
117 } catch (DatabaseException e) {
\r
118 LOGGER.error("Updating issue source failed.", e);
\r
121 Session session = Simantics.getSession();
\r
122 session.asyncRequest(new WriteRequest(vg) {
\r
124 public void perform(WriteGraph graph) throws DatabaseException {
\r
125 is.update(graph, resources);
\r
135 private static class IssueSourceManagedIssuesListener extends SingleSetSyncListener<Resource> {
\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
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
148 class IssueValidityListener extends DisposableSyncListener<Boolean> {
\r
150 final private Resource issue;
\r
152 public IssueValidityListener(Resource issue) {
\r
153 this.issue = issue;
\r
157 public void execute(ReadGraph graph, Boolean valid) throws DatabaseException {
\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
164 public void perform(WriteGraph graph) throws DatabaseException {
\r
165 Issue desc = graph.sync(new StandardIssueDescription(issue));
\r
168 Resource context = (Resource)desc.getMainContext();
\r
169 new DependencyIssueSynchronizer2(context, source).perform(graph);
\r
177 public void exception(ReadGraph graph, Throwable throwable) throws DatabaseException {
\r
178 LOGGER.error("IssueValidityListener received an exception.", throwable);
\r
184 public void add(ReadGraph graph, final Resource issue) throws DatabaseException {
\r
185 IssueValidityListener listener = new IssueValidityListener(issue);
\r
187 graph.asyncRequest(new ResourceRead3<Boolean>(issue, model, source) {
\r
190 public Boolean perform(ReadGraph graph) throws DatabaseException {
\r
191 Issue desc = graph.sync(new StandardIssueDescription(resource));
\r
194 Resource context = (Resource)desc.getMainContext();
\r
195 return graph.syncRequest(new DependencyIssueValidator2(context, resource2, resource3), TransientCacheListener.<Boolean>instance());
\r
200 listeners.put(issue, listener);
\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
211 public void exception(ReadGraph graph, Throwable t) {
\r
212 LOGGER.error("IssueSourceManagedIssuesListener received an exception.", t);
\r
216 public boolean isDisposed() {
\r
217 boolean disp = disposed.get();
\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
232 private static class ActiveIssueSourceListener extends SingleSetSyncListener<Resource> {
\r
234 private final AtomicBoolean disposed;
\r
235 private Map<Resource, Pair<IssueSource, IssueSourceDirtyListener>> sources = new HashMap<>();
\r
237 public ActiveIssueSourceListener(AtomicBoolean disposed) {
\r
238 this.disposed = disposed;
\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
248 IssueSourceDirtyListener listener = new IssueSourceDirtyListener(is);
\r
249 is.addDirtyListener(listener);
\r
250 sources.put(source, Pair.make(is, listener));
\r
252 if (isListeningTracker) {
\r
253 graph.asyncRequest(
\r
254 new Objects(source, ISSUE.IssueSource_Manages),
\r
255 new IssueSourceManagedIssuesListener(disposed, source, model));
\r
260 public void remove(ReadGraph graph, final Resource source) throws DatabaseException {
\r
261 Pair<IssueSource, IssueSourceDirtyListener> is = sources.remove(source);
\r
263 is.first.removeDirtyListener(is.second);
\r
267 public void exception(ReadGraph graph, Throwable t) {
\r
268 LOGGER.error("ActiveIssueSourceListener received an exception.", t);
\r
272 public boolean isDisposed() {
\r
273 return disposed.get();
\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
285 public void dispose() {
\r
286 disposed.set(true);
\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
297 return ListUtils.toList(graph, list);
\r
299 return Collections.emptyList();
\r
302 public static void writeAdditionalContext(WriteGraph graph, Resource issue, List<Resource> contexts) throws DatabaseException {
\r
304 if(contexts.isEmpty()) return;
\r
306 IssueResource IR = IssueResource.getInstance(graph);
\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
318 public static void newUserIssue(WriteGraph graph, String label, Resource severity, List<Resource> contexts) throws DatabaseException {
\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
323 newUserIssueForModel(graph, model, label, severity, contexts);
\r
327 public static Resource newUserIssueForModel(WriteGraph graph) throws DatabaseException {
\r
329 Resource project = Simantics.getProjectResource();
\r
330 Collection<Resource> activeModels = graph.syncRequest(new ActiveModels(project));
\r
331 if (activeModels.size() != 1)
\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
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
349 public static Resource newUserIssueForModel(WriteGraph graph, Resource model, Resource type, String label, Resource severity, List<Resource> contexts) throws DatabaseException {
\r
351 Layer0 L0 = Layer0.getInstance(graph);
\r
352 IssueResource ISSUE = IssueResource.getInstance(graph);
\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
371 * Creates a new issue based on SimpleIssue structure.
\r
373 public static void newSimpleIssueForModel(WriteGraph graph, Resource model, Resource issueType, List<Resource> contexts, SimpleIssue simpleIssue) throws DatabaseException {
\r
375 Layer0 L0 = Layer0.getInstance(graph);
\r
376 IssueResource ISSUE = IssueResource.getInstance(graph);
\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
393 * Returns the set of all issues with given type.
\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
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
404 List<Resource> curContexts =
\r
405 ListUtils.toList(graph, graph.getSingleObject(issue, ISSUE.Issue_HasContexts));
\r
406 if(!contexts.equals(curContexts))
\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
415 if(currentIssues == null)
\r
416 return Collections.emptySet();
\r
418 return currentIssues;
\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
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
431 newSimpleIssueForModel(graph, model, issueType, contexts, newIssue);
\r
433 for(SimpleIssue oldIssue : currentIssues)
\r
434 RemoverUtil.remove(graph, oldIssue.issueResource);
\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
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
445 setSimpleIssues(graph, model, contexts, issueType, issues);
\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
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
462 setSimpleIssuesAsync(graph, model, contexts, issueType, issues);
\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
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
479 boolean needsUpdating = false;
\r
480 if(issues.length != oldIssues.size())
\r
481 needsUpdating = true;
\r
483 for(SimpleIssue newIssue : issues) {
\r
484 if(!oldIssues.contains(newIssue)) {
\r
485 needsUpdating = true;
\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
495 public void perform(WriteGraph graph) throws DatabaseException {
\r
496 setSimpleIssues(graph, model, contexts, issueType, issues);
\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
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
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
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
523 sb.append(URIStringUtils.unescape(uri.substring(startIndex, nextSlash))).append(URIStringUtils.NAMESPACE_PATH_SEPARATOR);
\r
524 startIndex = nextSlash + 1;
\r
526 return sb.toString();
\r