/*******************************************************************************
* Copyright (c) 2006-2010 Vienna University of Technology,
* Department of Software Technology and Interactive Systems
*
* All rights reserved. This program and the accompanying
* materials are made available under the terms of the
* Apache License, Version 2.0 which accompanies
* this distribution, and is available at
* http://www.apache.org/licenses/LICENSE-2.0
*******************************************************************************/
package eu.planets_project.pp.plato.action.workflow;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.ejb.Remove;
import javax.ejb.Stateful;
import javax.faces.application.FacesMessage;
import org.apache.commons.logging.Log;
import org.jboss.annotation.ejb.cache.Cache;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Destroy;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Out;
import org.jboss.seam.annotations.RaiseEvent;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.contexts.Contexts;
import org.jboss.seam.faces.FacesMessages;
import eu.planets_project.pp.plato.action.interfaces.IEvaluateExperiments;
import eu.planets_project.pp.plato.action.interfaces.ITransformMeasuredValues;
import eu.planets_project.pp.plato.action.interfaces.IWorkflowStep;
import eu.planets_project.pp.plato.bean.BooleanCapsule;
import eu.planets_project.pp.plato.evaluation.IActionEvaluator;
import eu.planets_project.pp.plato.evaluation.IObjectEvaluator;
import eu.planets_project.pp.plato.evaluation.IStatusListener;
import eu.planets_project.pp.plato.evaluation.MiniRED;
import eu.planets_project.pp.plato.model.Alternative;
import eu.planets_project.pp.plato.model.DigitalObject;
import eu.planets_project.pp.plato.model.EvaluationStatus;
import eu.planets_project.pp.plato.model.PlanState;
import eu.planets_project.pp.plato.model.SampleObject;
import eu.planets_project.pp.plato.model.Values;
import eu.planets_project.pp.plato.model.measurement.MeasurableProperty;
import eu.planets_project.pp.plato.model.tree.Leaf;
import eu.planets_project.pp.plato.model.tree.Node;
import eu.planets_project.pp.plato.model.tree.TreeNode;
import eu.planets_project.pp.plato.model.values.Value;
import eu.planets_project.pp.plato.services.characterisation.jhove.JHoveAdaptor;
import eu.planets_project.pp.plato.services.characterisation.jhove.tree.JHoveTree;
import eu.planets_project.pp.plato.util.MeasurementInfoUri;
import eu.planets_project.pp.plato.util.PlatoLogger;
import eu.planets_project.pp.plato.validators.INodeValidator;
import eu.planets_project.pp.plato.validators.ITreeValidator;
import eu.planets_project.pp.plato.validators.TreeValidator;
/**
* Implements actions for workflow step 'Evaluate Experiments', i.e. enable the user
* to enter the evaluation result per alternative and sample record. The user has to enter
* evaluation result for all leaves in the objective tree.
* @author Hannes Kulovits
*/
@Stateful
@Scope(ScopeType.SESSION)
@Name("evalexperiments")
@Cache(org.jboss.ejb3.cache.NoPassivationCache.class)
public class EvaluateExperimentsAction extends AbstractWorkflowStep implements
IEvaluateExperiments, INodeValidator, IStatusListener{
/**
*
*/
private static final long serialVersionUID = -3892845379219070784L;
protected boolean needsClearEm() {
return true;
}
private static final Log log = PlatoLogger.getLogger(EvaluateExperimentsAction.class);
private StringBuffer evaluationLogBuffer;
@In(create = true)
ITransformMeasuredValues transform;
@SuppressWarnings("unused")
@Out(required=false)
private JHoveTree jhoveTree1;
@SuppressWarnings("unused")
@Out(required=false)
private JHoveTree jhoveTree2;
/**
* Creates from the SampleObject object, retrieved by injection, a Tree with all the
* characteristics extracted from Jhove
*
*/
public JHoveTree characteriseJHove(DigitalObject object) {
JHoveTree jhoveTree = new JHoveTree();
//returns an empty tree for null
if(object==null) {
return jhoveTree;
}
if (object.getJhoveXMLString() == null || "".equals(object.getJhoveXMLString())) {
object.setJhoveXMLString(new JHoveAdaptor().describe(object));
}
if(object.getJhoveXMLString()!=null && !"".equals(object.getJhoveXMLString())) {
jhoveTree= new JHoveAdaptor().digestString(object.getFullname(),object.getJhoveXMLString());
}
return jhoveTree;
}
/**
* the node currently selected in the objective tree.
*/
@In(required = false)
@Out(required = false)
TreeNode node;
public EvaluateExperimentsAction() {
requiredPlanState = new Integer(PlanState.EXPERIMENT_PERFORMED);
}
/**
* List of all leaves for which the evaluation settings shall be displayed to the user.
*/
@Out(required = false)
@In(required = false)
List<Leaf> leaves;
@Out
private List<MeasurableProperty> measurableProperties = new ArrayList<MeasurableProperty>();
/**
* Leaves displayed to the user.
*/
@Out(required = false)
@In(required=false)
List<Leaf> errorleaves;
@Out
private BooleanCapsule hasAutomatedMeasurements = new BooleanCapsule();
protected IWorkflowStep getSuccessor() {
return transform;
}
/**
* initialises the values in the tree and inits/clears the leaf list
* @see AbstractWorkflowStep#init()
*/
public void init() {
clearLogBuffer();
initLeafLists();
boolean xcl = false;
hasAutomatedMeasurements.setBool(false);
Iterator<Leaf> iter = selectedPlan.getTree().getRoot().getAllLeaves().iterator();
while (iter.hasNext()) {
Leaf l = iter.next();
if (l.isMapped()) {
hasAutomatedMeasurements.setBool(true);
if (l.getMeasurementInfo().getProperty().getName().startsWith("xcl/")) {
xcl = true;
}
}
}
if (xcl) {
for (DigitalObject o : selectedPlan.getSampleRecordsDefinition().getRecords()) {
if ((o.getXcdlDescription() == null)|| !o.getXcdlDescription().isDataExistent()) {
FacesMessages.instance().add(FacesMessage.SEVERITY_INFO,
"Some XCL descriptions for samples are missing, XCL comparison will not work for these samples. ");
break;
}
}
for (Alternative a: selectedPlan.getAlternativesDefinition().getAlternatives()) {
for (DigitalObject r: a.getExperiment().getResults().values()) {
if ((r.getXcdlDescription() == null)|| !r.getXcdlDescription().isDataExistent()) {
FacesMessages.instance().add(FacesMessage.SEVERITY_INFO,
"XCL descriptions for experiment results of "+ a.getName() +" are missing, " +
"XCL comparison will not work for these objects. ");
break;
}
}
}
}
// TODO for now this is ALWAYS reloaded when entering,
// but should be cached in the future.
MiniRED.getInstance().reloadEvaluators();
refreshMeasurableProperties();
}
private void clearLogBuffer() {
evaluationLogBuffer = new StringBuffer();
Contexts.getEventContext().remove("evaluationMessage");
}
/**
* @param record sample object the alternative has been carried out on
* @param alternative alternative which has been carried out on the alternative (we determine the result object from the alternative)
*/
public void setTreeFromRecordAltern(Object record, Object alternative){
if (!(record instanceof SampleObject) || !(alternative instanceof Alternative)) {
return;
}
Alternative a = (Alternative)alternative;
SampleObject o = (SampleObject)record;
o = em.merge(o);
jhoveTree1=characteriseJHove(o);
// get the result
// we have to merge the sample object back into the session to be able to access the byte stream
DigitalObject result = em.merge(a.getExperiment().getResults().get(record));
a.getExperiment().getResults().put(o, result);
jhoveTree2=characteriseJHove(result);
}
/**
* Select a node or leaf from the tree.
*/
public String select(Object ob) {
initErrorLeaves();
log.trace("Select Called with: " + ob.toString());
if (ob instanceof Node) {
log.debug("Setting all Leaves");
leaves = ((Node) ob).getAllLeaves();
} else if (ob instanceof Leaf) {
log.debug("Setting leaf: " + ob.toString());
leaves.clear();
leaves.add((Leaf) ob);
}
return null;
}
/**
* @see AbstractWorkflowStep#discard()
*/
@Override
@RaiseEvent("reload")
public String discard() {
String result = super.discard();
init();
return result;
}
/**
* @see AbstractWorkflowStep#destroy()
*/
@Destroy
@Remove
public void destroy() {
}
/**
* @see eu.planets_project.pp.plato.action.workflow.AbstractWorkflowStep#validate()
*/
public boolean validate(boolean showValidationErrors) {
ITreeValidator validator = new TreeValidator();
List<TreeNode> nodes = new ArrayList<TreeNode>();
boolean valid = validator.validate(selectedPlan.getTree().getRoot(),
this, nodes, showValidationErrors);
if (!valid) {
if (leaves == null) {
leaves = new ArrayList<Leaf>();
} else {
leaves.clear();
}
//All invalid leaves should be in the list so that they are displayed
for (TreeNode node : nodes) {
if (node.isLeaf()) {
this.leaves.add((Leaf) node);
}
}
}
return valid;
}
/**
* @see eu.planets_project.pp.plato.util.INodeValidator#validateNode(eu.planets_project.pp.plato.model.TreeNode, java.util.List, java.util.List)
*/
public boolean validateNode(TreeNode node, List<String> nodelist,
List<TreeNode> nodes) {
return node.isCompletelyEvaluated(nodelist, nodes, selectedPlan.getAlternativesDefinition().getConsideredAlternatives());
}
/**
* We have the rule that all evaluation settings have to be either changed or confirmed once
* by the user.
* This approve function makes it easier to confirm the settings for many leaves at once -
* It touches all currently displayed leaves so that they are marked as confirmed.
* @see eu.planets_project.pp.plato.model.values.Value#touch()
*/
public void approve() {
for (Leaf leaf : leaves) {
for (Values values : leaf.getValueMap().values()) {
for (Value value : values.getList()) {
value.touch();
}
}
}
}
/**
* @see AbstractWorkflowStep#getWorkflowstepName()
*/
protected String getWorkflowstepName() {
return "evalexperiments";
}
/**
* evaluates the given leaves automatically.
* This is only possible for criteria, where information on the measurement has been defined.
* The registered evaluators are applied one after an other,
* if an evaluator is able to measure a criterion, its value is applied and the criterion is excluded from further evaluation.
*
* First per alternative all action related evaluators are called.
*
* Then per alternative, for each sample object, all object/runtime related evaluators are called.
*
* @param leaves
*/
private void evaluateLeaves(List<Leaf> leaves) {
clearLogBuffer();
// we evaluate measurements and have to assign each result to the corresponding leaf: build a map
HashMap<MeasurementInfoUri, Leaf> measurementOfLeaf = new HashMap<MeasurementInfoUri, Leaf>();
// list of measurements which shall be evaluated
List<MeasurementInfoUri> allMeasurementsToEval = new LinkedList<MeasurementInfoUri>();
for(Leaf l : leaves) {
// measure this criterion automatically
MeasurementInfoUri m = l.getMeasurementInfo().toMeasurementInfoUri();
if ((m != null) && (m.getAsURI() != null)) {
measurementOfLeaf.put(m , l);
allMeasurementsToEval.add(m);
}
}
try {
// start evaluation:
List<MeasurementInfoUri> measurementsToEval = new ArrayList<MeasurementInfoUri>();
// first action evaluators
List<IActionEvaluator> actionEvaluators = MiniRED.getInstance().getActionEvaluationSequence();
for (Alternative alternative : selectedPlan.getAlternativesDefinition().getConsideredAlternatives()) {
// we want to evaluate each property only once, by the evaluator with the highest priority
measurementsToEval.clear();
measurementsToEval.addAll(allMeasurementsToEval);
for (IActionEvaluator evaluator : actionEvaluators) {
Map<MeasurementInfoUri, Value> results = evaluator.evaluate(alternative, measurementsToEval, this);
// apply all results
for (MeasurementInfoUri m : results.keySet()) {
Value value = results.get(m);
if (value != null) {
Leaf l = measurementOfLeaf.get(m);
value.setScale(l.getScale());
l.getValues(alternative.getName()).setValue(0,value);
}
}
// exclude evaluated leaves from further evaluation
measurementsToEval.removeAll(results.keySet());
}
}
// then object evaluators
List<IObjectEvaluator> objEvaluators = MiniRED.getInstance().getObjectEvaluationSequence();
for (Alternative alternative : selectedPlan.getAlternativesDefinition().getConsideredAlternatives()) {
// .. for all alternatives
List<SampleObject> samples = selectedPlan.getSampleRecordsDefinition().getRecords();
for (int i = 0; i < samples.size(); i++){
// we want to evaluate each property only once, by the evaluator with the highest priority
measurementsToEval.clear();
measurementsToEval.addAll(allMeasurementsToEval);
for (IObjectEvaluator evaluator : objEvaluators) {
DigitalObject r = alternative.getExperiment().getResults().get(samples.get(i));
DigitalObject r2 = (r == null ? null : em.merge(r));
try {
Map<MeasurementInfoUri, Value> results = evaluator.evaluate(
alternative,
em.merge(samples.get(i)),
r2,
measurementsToEval,
this);
// apply all results
for (MeasurementInfoUri m : results.keySet()) {
Value value = results.get(m);
if (value != null) {
Leaf l = measurementOfLeaf.get(m);
value.setScale(l.getScale());
// add evaluation result for the current result-object!
l.getValues(alternative.getName()).setValue(i, value);
}
}
// exclude evaluated leaves from further evaluation
measurementsToEval.removeAll(results.keySet());
} catch (Exception e) {
log.error("evaluator failed" + e.getMessage(),e);
continue;
}
}
}
}
} catch (Exception e) {
log.error("Automated evaluation threw exception "+e.getMessage(),e);
FacesMessages.instance().add(FacesMessage.SEVERITY_ERROR, "Automated evaluation failed:"+ e.getMessage());
updateStatus("Automated evaluation threw exception "+e.getMessage());
}
Contexts.getEventContext().set("evaluationMessage", evaluationLogBuffer.toString());
}
public void evaluateAll() {
evaluateLeaves(selectedPlan.getTree().getRoot().getAllLeaves());
}
public void evaluate(Leaf leaf) {
evaluateLeaves(Arrays.asList(leaf));
}
private void refreshMeasurableProperties() {
measurableProperties.clear();
measurableProperties.addAll(selectedPlan.getMeasurableProperties());
for (MeasurableProperty p: measurableProperties) {
log.debug("prop:: "+p.getName());
}
}
public void updateStatus(String msg) {
log.info(msg);
evaluationLogBuffer.append(msg).append("\n");
}
@Override
public String save() {
// initialising the values for free text transformers
for (Leaf l:selectedPlan.getTree().getRoot().getAllLeaves()) {
l.initTransformer();
}
return super.save();
}
/**
*
*/
private void initErrorLeaves() {
if(errorleaves == null){
errorleaves = new ArrayList<Leaf>();
} else {
errorleaves.clear();
}
}
/**
*
*/
private void initLeafLists() {
if(leaves == null){
leaves = new ArrayList<Leaf>();
} else {
leaves.clear();
}
initErrorLeaves();
}
}