blob: 825efeedd90db986e0f99b7e72c332aaa62786cc [file] [log] [blame]
/**
*
*/
package net.sf.taverna.t2.workbench.report.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import net.sf.taverna.t2.lang.observer.MultiCaster;
import net.sf.taverna.t2.lang.observer.Observable;
import net.sf.taverna.t2.lang.observer.Observer;
import net.sf.taverna.t2.visit.HierarchyTraverser;
import net.sf.taverna.t2.visit.VisitKind;
import net.sf.taverna.t2.visit.VisitReport;
import net.sf.taverna.t2.visit.VisitReport.Status;
import net.sf.taverna.t2.visit.Visitor;
import net.sf.taverna.t2.workbench.edits.EditManager;
import net.sf.taverna.t2.workbench.edits.EditManager.AbstractDataflowEditEvent;
import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
import net.sf.taverna.t2.workbench.file.FileManager;
import net.sf.taverna.t2.workbench.file.events.ClosedDataflowEvent;
import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
import net.sf.taverna.t2.workbench.file.events.SetCurrentDataflowEvent;
import net.sf.taverna.t2.workbench.report.DataflowReportEvent;
import net.sf.taverna.t2.workbench.report.FailedEntityKind;
import net.sf.taverna.t2.workbench.report.IncompleteDataflowKind;
import net.sf.taverna.t2.workbench.report.InvalidDataflowKind;
import net.sf.taverna.t2.workbench.report.ReportManager;
import net.sf.taverna.t2.workbench.report.ReportManagerEvent;
import net.sf.taverna.t2.workbench.report.UnresolvedOutputKind;
import net.sf.taverna.t2.workbench.report.UnsatisfiedEntityKind;
import net.sf.taverna.t2.workbench.report.config.ReportManagerConfiguration;
import net.sf.taverna.t2.workflowmodel.Dataflow;
import net.sf.taverna.t2.workflowmodel.DataflowValidationReport;
import net.sf.taverna.t2.workflowmodel.Processor;
import org.apache.log4j.Logger;
/**
* @author Alan R Williams
*
*/
public class ReportManagerImpl implements Observable<ReportManagerEvent>, ReportManager {
private static final long MAX_AGE_OUTDATED_MILLIS = 1 * 60 * 60 * 1000; // 1 hour
private static Logger logger = Logger.getLogger(ReportManagerImpl.class);
private ReportManagerConfiguration reportManagerConfiguration;
private HierarchyTraverser traverser = null;
private Map<Dataflow, Map<Object, Set<VisitReport>>> reportMap = Collections
.synchronizedMap(new WeakHashMap<Dataflow, Map<Object, Set<VisitReport>>>());;
private Map<Dataflow, Map<Object, Status>> statusMap = Collections
.synchronizedMap(new WeakHashMap<Dataflow, Map<Object, Status>>());
private Map<Dataflow, Map<Object, String>> summaryMap = Collections
.synchronizedMap(new WeakHashMap<Dataflow, Map<Object, String>>());
private Map<Dataflow, Long> lastCheckedMap = Collections
.synchronizedMap(new WeakHashMap<Dataflow, Long>());
private Map<Dataflow, Long> lastFullCheckedMap = Collections
.synchronizedMap(new WeakHashMap<Dataflow, Long>());
private Map<Dataflow, String> lastFullCheckedDataflowIdMap = Collections
.synchronizedMap(new WeakHashMap<Dataflow, String>());
private EditManager editManager;
private FileManager fileManager;
// private Set<Visitor<?>> visitors;
protected ReportManagerImpl(EditManager editManager, FileManager fileManager,
Set<Visitor<?>> visitors, ReportManagerConfiguration reportManagerConfiguration)
throws IllegalStateException {
this.editManager = editManager;
this.fileManager = fileManager;
this.reportManagerConfiguration = reportManagerConfiguration;
// this.visitors = visitors;
traverser = new HierarchyTraverser(visitors);
ReportManagerFileObserver fileObserver = new ReportManagerFileObserver();
fileManager.addObserver(fileObserver);
addEditObserver();
reportManagerConfiguration.applySettings();
}
private void addEditObserver() {
synchronized (editManager) {
List<Observer<EditManagerEvent>> currentObservers = editManager.getObservers();
for (Observer<EditManagerEvent> o : currentObservers) {
editManager.removeObserver(o);
}
editManager.addObserver(new ReportManagerEditObserver());
for (Observer<EditManagerEvent> o : currentObservers) {
editManager.addObserver(o);
}
}
}
/*
* (non-Javadoc)
*
* @see
* net.sf.taverna.t2.workbench.report.ReportManagerI#updateReport(net.sf.taverna.t2.workflowmodel
* .Dataflow, boolean, boolean)
*/
@Override
public void updateReport(Dataflow d, boolean includeTimeConsuming, boolean remember) {
Set<VisitReport> oldTimeConsumingReports = null;
long time = System.currentTimeMillis();
long expiration = Integer.parseInt(reportManagerConfiguration
.getProperty(ReportManagerConfiguration.REPORT_EXPIRATION)) * 60 * 1000;
if (remember && !includeTimeConsuming
&& ((expiration == 0) || ((time - getLastFullCheckedTime(d)) < expiration))) {
oldTimeConsumingReports = getTimeConsumingReports(d);
}
Map<Object, Set<VisitReport>> reportsEntry = new HashMap<Object, Set<VisitReport>>();
Map<Object, Status> statusEntry = new HashMap<Object, Status>();
Map<Object, String> summaryEntry = new HashMap<Object, String>();
reportMap.put(d, reportsEntry);
statusMap.put(d, statusEntry);
summaryMap.put(d, summaryEntry);
validateDataflow(d, reportsEntry, statusEntry, summaryEntry);
Set<VisitReport> newReports = new HashSet<VisitReport>();
traverser.traverse(d, new ArrayList<Object>(), newReports, includeTimeConsuming);
for (VisitReport vr : newReports) {
addReport(reportsEntry, statusEntry, summaryEntry, vr);
}
if (oldTimeConsumingReports != null) {
for (VisitReport vr : oldTimeConsumingReports) {
addReport(reportsEntry, statusEntry, summaryEntry, vr);
}
}
time = System.currentTimeMillis();
lastCheckedMap.put(d, time);
if (includeTimeConsuming) {
lastFullCheckedMap.put(d, time);
lastFullCheckedDataflowIdMap.put(d, d.getIdentifier());
}
multiCaster.notify(new DataflowReportEvent(d));
}
private void updateObjectReportInternal(Dataflow d, Object o) {
Map<Object, Set<VisitReport>> reportsEntry = reportMap.get(d);
Map<Object, Status> statusEntry = statusMap.get(d);
Map<Object, String> summaryEntry = summaryMap.get(d);
if (reportsEntry == null) {
logger.error("Attempt to update reports on an object in a dataflow that has not been checked");
reportsEntry = new HashMap<Object, Set<VisitReport>>();
statusEntry = new HashMap<Object, Status>();
summaryEntry = new HashMap<Object, String>();
reportMap.put(d, reportsEntry);
statusMap.put(d, statusEntry);
summaryMap.put(d, summaryEntry);
} else {
reportsEntry.remove(o);
statusEntry.remove(o);
summaryEntry.remove(o);
}
// Assume o is directly inside d
List<Object> ancestry = new ArrayList<Object>();
ancestry.add(d);
Set<VisitReport> newReports = new HashSet<VisitReport>();
traverser.traverse(o, ancestry, newReports, true);
for (VisitReport vr : newReports) {
addReport(reportsEntry, statusEntry, summaryEntry, vr);
}
}
/*
* (non-Javadoc)
*
* @see
* net.sf.taverna.t2.workbench.report.ReportManagerI#updateObjectSetReport(net.sf.taverna.t2
* .workflowmodel.Dataflow, java.util.Set)
*/
@Override
public void updateObjectSetReport(Dataflow d, Set<Object> objects) {
for (Object o : objects) {
updateObjectReportInternal(d, o);
}
multiCaster.notify(new DataflowReportEvent(d));
}
/*
* (non-Javadoc)
*
* @see net.sf.taverna.t2.workbench.report.ReportManagerI#updateObjectReport(net.sf.taverna.t2.
* workflowmodel.Dataflow, java.lang.Object)
*/
@Override
public void updateObjectReport(Dataflow d, Object o) {
updateObjectReportInternal(d, o);
multiCaster.notify(new DataflowReportEvent(d));
}
private Set<VisitReport> getTimeConsumingReports(Dataflow d) {
Set<VisitReport> result = new HashSet<VisitReport>();
Map<Object, Set<VisitReport>> currentReports = getReports(d);
if (currentReports != null) {
for (Object o : currentReports.keySet()) {
for (VisitReport vr : currentReports.get(o)) {
if (vr.wasTimeConsuming()) {
result.add(vr);
}
}
}
}
return result;
}
private void removeReport(Dataflow d) {
reportMap.remove(d);
statusMap.remove(d);
summaryMap.remove(d);
}
private void addReport(Map<Object, Set<VisitReport>> reports, Map<Object, Status> statusEntry,
Map<Object, String> summaryEntry, VisitReport newReport) {
if (newReport.getCheckTime() == 0) {
newReport.setCheckTime(System.currentTimeMillis());
}
Object subject = newReport.getSubject();
Set<VisitReport> currentReports = reports.get(subject);
Status newReportStatus = newReport.getStatus();
if (currentReports == null) {
currentReports = new HashSet<VisitReport>();
reports.put(subject, currentReports);
statusEntry.put(subject, newReportStatus);
summaryEntry.put(subject, newReport.getMessage());
} else {
Status currentStatus = statusEntry.get(subject);
if (currentStatus.compareTo(newReportStatus) < 0) {
statusEntry.put(subject, newReportStatus);
summaryEntry.put(subject, newReport.getMessage());
} else if (currentStatus.compareTo(newReportStatus) == 0) {
if (currentStatus.equals(Status.WARNING)) {
summaryEntry.put(subject, "Multiple warnings");
} else if (currentStatus.equals(Status.SEVERE)) {
summaryEntry.put(subject, "Multiple errors");
}
}
}
currentReports.add(newReport);
}
private void validateDataflow(Dataflow d, Map<Object, Set<VisitReport>> reportsEntry,
Map<Object, Status> statusEntry, Map<Object, String> summaryEntry) {
DataflowValidationReport validationReport = d.checkValidity();
if (validationReport.isWorkflowIncomplete()) {
addReport(reportsEntry, statusEntry, summaryEntry, new VisitReport(
IncompleteDataflowKind.getInstance(), d, "Incomplete workflow",
IncompleteDataflowKind.INCOMPLETE_DATAFLOW, VisitReport.Status.SEVERE));
} else if (!validationReport.isValid()) {
addReport(reportsEntry, statusEntry, summaryEntry,
new VisitReport(InvalidDataflowKind.getInstance(), d, "Invalid workflow",
InvalidDataflowKind.INVALID_DATAFLOW, VisitReport.Status.SEVERE));
}
fillInReport(reportsEntry, statusEntry, summaryEntry, validationReport);
}
private void fillInReport(Map<Object, Set<VisitReport>> reportsEntry,
Map<Object, Status> statusEntry, Map<Object, String> summaryEntry,
DataflowValidationReport report) {
for (Object o : report.getUnresolvedOutputs()) {
addReport(reportsEntry, statusEntry, summaryEntry,
new VisitReport(UnresolvedOutputKind.getInstance(), o,
"Invalid workflow output", UnresolvedOutputKind.OUTPUT,
VisitReport.Status.SEVERE));
}
for (Object o : report.getFailedEntities()) {
addReport(reportsEntry, statusEntry, summaryEntry,
new VisitReport(FailedEntityKind.getInstance(), o,
"Mismatch of input list depths", FailedEntityKind.FAILED_ENTITY,
VisitReport.Status.SEVERE));
}
for (Object o : report.getUnsatisfiedEntities()) {
addReport(reportsEntry, statusEntry, summaryEntry, new VisitReport(
UnsatisfiedEntityKind.getInstance(), o, "Unknown prior list depth",
UnsatisfiedEntityKind.UNSATISFIED_ENTITY, VisitReport.Status.SEVERE));
}
// for (DataflowValidationReport subReport : report.getInvalidDataflows().values()) {
// fillInReport(descriptionMap, subReport);
// }
}
/*
* (non-Javadoc)
*
* @see
* net.sf.taverna.t2.workbench.report.ReportManagerI#getReports(net.sf.taverna.t2.workflowmodel
* .Dataflow, java.lang.Object)
*/
@Override
public Set<VisitReport> getReports(Dataflow d, Object object) {
Set<VisitReport> result = new HashSet<VisitReport>();
Map<Object, Set<VisitReport>> objectReports = reportMap.get(d);
if (objectReports != null) {
Set<Object> objects = new HashSet<Object>();
objects.add(object);
if (object instanceof Processor) {
objects.addAll(((Processor) object).getActivityList());
}
for (Object o : objects) {
if (objectReports.containsKey(o)) {
result.addAll(objectReports.get(o));
}
}
}
return result;
}
/*
* (non-Javadoc)
*
* @see
* net.sf.taverna.t2.workbench.report.ReportManagerI#getReports(net.sf.taverna.t2.workflowmodel
* .Dataflow)
*/
@Override
public Map<Object, Set<VisitReport>> getReports(Dataflow d) {
return reportMap.get(d);
}
/*
* (non-Javadoc)
*
* @see net.sf.taverna.t2.workbench.report.ReportManagerI#isStructurallySound(net.sf.taverna.t2.
* workflowmodel.Dataflow)
*/
@Override
public boolean isStructurallySound(Dataflow d) {
Map<Object, Set<VisitReport>> objectReports = reportMap.get(d);
if (objectReports == null) {
return false;
}
for (Set<VisitReport> visitReportSet : objectReports.values()) {
for (VisitReport vr : visitReportSet) {
if (vr.getStatus().equals(Status.SEVERE)) {
VisitKind vk = vr.getKind();
if ((vk instanceof IncompleteDataflowKind)
|| (vk instanceof InvalidDataflowKind)
|| (vk instanceof UnresolvedOutputKind)
|| (vk instanceof FailedEntityKind)
|| (vk instanceof UnsatisfiedEntityKind)) {
return false;
}
}
}
}
return true;
}
/*
* (non-Javadoc)
*
* @see
* net.sf.taverna.t2.workbench.report.ReportManagerI#getStatus(net.sf.taverna.t2.workflowmodel
* .Dataflow)
*/
@Override
public Status getStatus(Dataflow d) {
Map<Object, Set<VisitReport>> objectReports = reportMap.get(d);
if (objectReports == null) {
return Status.OK;
}
Status currentStatus = Status.OK;
for (Set<VisitReport> visitReportSet : objectReports.values()) {
for (VisitReport vr : visitReportSet) {
Status status = vr.getStatus();
if (status.compareTo(currentStatus) > 0) {
currentStatus = status;
}
if (currentStatus.equals(Status.SEVERE)) {
return currentStatus;
}
}
}
return currentStatus;
}
/*
* (non-Javadoc)
*
* @see
* net.sf.taverna.t2.workbench.report.ReportManagerI#getStatus(net.sf.taverna.t2.workflowmodel
* .Dataflow, java.lang.Object)
*/
@Override
public Status getStatus(Dataflow d, Object object) {
Status result = Status.OK;
Map<Object, Status> statusEntry = statusMap.get(d);
if (statusEntry != null) {
Status value = statusEntry.get(object);
if (value != null) {
result = value;
}
}
return result;
}
/*
* (non-Javadoc)
*
* @see net.sf.taverna.t2.workbench.report.ReportManagerI#getSummaryMessage(net.sf.taverna.t2.
* workflowmodel.Dataflow, java.lang.Object)
*/
@Override
public String getSummaryMessage(Dataflow d, Object object) {
String result = null;
if (!getStatus(d, object).equals(Status.OK)) {
Map<Object, String> summaryEntry = summaryMap.get(d);
if (summaryEntry != null) {
result = summaryEntry.get(object);
}
}
return result;
}
/*
* (non-Javadoc)
*
* @see net.sf.taverna.t2.workbench.report.ReportManagerI#getLastCheckedTime(net.sf.taverna.t2.
* workflowmodel.Dataflow)
*/
@Override
public long getLastCheckedTime(Dataflow d) {
Long l = lastCheckedMap.get(d);
if (l == null) {
return 0;
} else {
return l.longValue();
}
}
/*
* (non-Javadoc)
*
* @see
* net.sf.taverna.t2.workbench.report.ReportManagerI#getLastFullCheckedTime(net.sf.taverna.t2
* .workflowmodel.Dataflow)
*/
@Override
public long getLastFullCheckedTime(Dataflow d) {
Long l = lastFullCheckedMap.get(d);
if (l == null) {
return 0;
} else {
return l.longValue();
}
}
/**
* @author alanrw
*
*/
public class ReportManagerFileObserver implements Observer<FileManagerEvent> {
public void notify(Observable<FileManagerEvent> sender, FileManagerEvent message)
throws Exception {
String onOpen = reportManagerConfiguration.getProperty(
ReportManagerConfiguration.ON_OPEN);
if (message instanceof ClosedDataflowEvent) {
ReportManagerImpl.this.removeReport(((ClosedDataflowEvent) message).getDataflow());
} else if (message instanceof SetCurrentDataflowEvent) {
Dataflow dataflow = ((SetCurrentDataflowEvent) message).getDataflow();
if (!reportMap.containsKey(dataflow)) {
if (!onOpen.equals(ReportManagerConfiguration.NO_CHECK)) {
updateReport(dataflow,
onOpen.equals(ReportManagerConfiguration.FULL_CHECK), true);
} else {
ReportManagerImpl.this.multiCaster
.notify(new DataflowReportEvent(dataflow));
}
} else {
ReportManagerImpl.this.multiCaster.notify(new DataflowReportEvent(dataflow));
}
}
}
}
private MultiCaster<ReportManagerEvent> multiCaster = new MultiCaster<ReportManagerEvent>(this);
/*
* (non-Javadoc)
*
* @see
* net.sf.taverna.t2.workbench.report.ReportManagerI#addObserver(net.sf.taverna.t2.lang.observer
* .Observer)
*/
@Override
public void addObserver(Observer<ReportManagerEvent> observer) {
multiCaster.addObserver(observer);
}
/*
* (non-Javadoc)
*
* @see net.sf.taverna.t2.workbench.report.ReportManagerI#getObservers()
*/
@Override
public List<Observer<ReportManagerEvent>> getObservers() {
return multiCaster.getObservers();
}
/*
* (non-Javadoc)
*
* @see
* net.sf.taverna.t2.workbench.report.ReportManagerI#removeObserver(net.sf.taverna.t2.lang.observer
* .Observer)
*/
@Override
public void removeObserver(Observer<ReportManagerEvent> observer) {
multiCaster.removeObserver(observer);
}
/*
* (non-Javadoc)
*
* @see net.sf.taverna.t2.workbench.report.ReportManagerI#isReportOutdated(net.sf.taverna.t2.
* workflowmodel.Dataflow)
*/
@Override
public boolean isReportOutdated(Dataflow dataflow) {
String lastCheckedId = lastFullCheckedDataflowIdMap.get(dataflow);
Long lastCheck = lastFullCheckedMap.get(dataflow);
if (lastCheckedId == null || lastCheck == null) {
// Unknown, so outdated
return true;
}
if (!lastCheckedId.equals(dataflow.getIdentifier())) {
// Workflow changed, so outdaeted
return true;
}
long now = System.currentTimeMillis();
long age = now - lastCheck;
// Outdated if it is older than the maximum
return age > MAX_AGE_OUTDATED_MILLIS;
}
public class ReportManagerEditObserver implements Observer<EditManagerEvent> {
public void notify(Observable<EditManagerEvent> sender, EditManagerEvent message)
throws Exception {
String onEdit = reportManagerConfiguration
.getProperty(ReportManagerConfiguration.ON_EDIT);
Dataflow dataflow = fileManager.getCurrentDataflow();
if (message instanceof AbstractDataflowEditEvent) {
AbstractDataflowEditEvent adee = (AbstractDataflowEditEvent) message;
if (adee.getDataFlow().equals(dataflow)) {
if (onEdit.equals(ReportManagerConfiguration.QUICK_CHECK)) {
updateReport(dataflow, false, true);
} else if (onEdit.equals(ReportManagerConfiguration.FULL_CHECK)) {
updateReport(dataflow, true, true);
}
}
}
}
}
}