/******************************************************************************* * Copyright 2006 - 2014 Vienna University of Technology, * Department of Software Technology and Interactive Systems, IFS * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * This work originates from the Planets project, co-funded by the European Union under the Sixth Framework Programme. ******************************************************************************/ package eu.scape_project.planning.model; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OneToOne; import javax.persistence.Transient; import javax.validation.Valid; import eu.scape_project.planning.exception.PlanningException; import eu.scape_project.planning.model.aggregators.WeightedMultiplication; import eu.scape_project.planning.model.beans.ResultNode; import eu.scape_project.planning.model.measurement.Measurement; import eu.scape_project.planning.model.scales.BooleanScale; import eu.scape_project.planning.model.transform.NumericTransformer; import eu.scape_project.planning.model.transform.OrdinalTransformer; import eu.scape_project.planning.model.transform.TransformationMode; import eu.scape_project.planning.model.transform.Transformer; import eu.scape_project.planning.model.tree.Leaf; import eu.scape_project.planning.model.tree.Node; import eu.scape_project.planning.model.tree.ObjectiveTree; import eu.scape_project.planning.model.tree.TreeNode; import eu.scape_project.planning.model.values.INumericValue; import eu.scape_project.planning.model.values.Value; /** * This is a preservation planning project, the root class of all domain model * data. Please refer to the terminology in deliverable PP4-D1 for an * explanation of terms. * * @author Christoph Becker */ @Entity public class Plan implements Serializable, ITouchable { private static final long serialVersionUID = 7855716962826361459L; /** * the Go/No-Go-decision */ @OneToOne(cascade = CascadeType.ALL) private Decision decision = new Decision(); @Id @GeneratedValue private int id; @OneToOne(cascade = CascadeType.ALL) private PlanProperties planProperties = new PlanProperties(); @OneToOne(cascade = CascadeType.ALL) private SampleRecordsDefinition sampleRecordsDefinition = new SampleRecordsDefinition(); @OneToOne(cascade = CascadeType.ALL) private ProjectBasis projectBasis = new ProjectBasis(); @OneToOne(cascade = CascadeType.ALL) private RequirementsDefinition requirementsDefinition = new RequirementsDefinition(); @Valid @OneToOne(cascade = CascadeType.ALL) private ObjectiveTree tree = new ObjectiveTree(); @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL) @JoinColumn(name = "alternativesdefinition_id") private AlternativesDefinition alternativesDefinition = new AlternativesDefinition(); @OneToOne(cascade = CascadeType.ALL) private Evaluation evaluation = new Evaluation(); @OneToOne(cascade = CascadeType.ALL) private Transformation transformation = new Transformation(); @OneToOne(cascade = CascadeType.ALL) private ImportanceWeighting importanceWeighting = new ImportanceWeighting(); @OneToOne(cascade = CascadeType.ALL) private Recommendation recommendation = new Recommendation(); @OneToOne(cascade = CascadeType.ALL) private ExecutablePlanDefinition executablePlanDefinition = new ExecutablePlanDefinition(); @OneToOne(cascade = CascadeType.ALL) private DigitalObject preservationActionPlan = new DigitalObject(); @OneToOne(cascade = CascadeType.ALL) private PlanDefinition planDefinition = new PlanDefinition(); @OneToOne(cascade = CascadeType.ALL) private ChangeLog changeLog = new ChangeLog(); /** * States if the plan was opened in read only mode */ @Transient private boolean readOnly = false; public Plan() { TreeNode root = new Node(); root.setName("Root"); getTree().setRoot(root); } public int getId() { return id; } public void setId(int id) { this.id = id; } @Transient public String getStateName() { return this.getPlanProperties().getState().getName(); } public ObjectiveTree getTree() { return tree; } public void setTree(ObjectiveTree tree) { this.tree = tree; } public Decision getDecision() { return decision; } public void setDecision(Decision decision) { this.decision = decision; } public SampleRecordsDefinition getSampleRecordsDefinition() { return sampleRecordsDefinition; } public void setSampleRecordsDefinition(SampleRecordsDefinition sampleRecords) { this.sampleRecordsDefinition = sampleRecords; } public PlanProperties getPlanProperties() { return planProperties; } public void setPlanProperties(PlanProperties planProperties) { this.planProperties = planProperties; } public ProjectBasis getProjectBasis() { return projectBasis; } public void setProjectBasis(ProjectBasis projectBasis) { this.projectBasis = projectBasis; } public ExecutablePlanDefinition getExecutablePlanDefinition() { return executablePlanDefinition; } public void setExecutablePlanDefinition(ExecutablePlanDefinition executablePlanDefinition) { this.executablePlanDefinition = executablePlanDefinition; } public DigitalObject getPreservationActionPlan() { return preservationActionPlan; } public void setPreservationActionPlan(DigitalObject preservationActionPlan) { this.preservationActionPlan = preservationActionPlan; } public PlanDefinition getPlanDefinition() { return planDefinition; } public void setPlanDefinition(PlanDefinition planDefinition) { this.planDefinition = planDefinition; } public AlternativesDefinition getAlternativesDefinition() { return alternativesDefinition; } public void setAlternativesDefinition(AlternativesDefinition alternativesDefinition) { this.alternativesDefinition = alternativesDefinition; } public Evaluation getEvaluation() { return evaluation; } public void setEvaluation(Evaluation evaluation) { this.evaluation = evaluation; } public Transformation getTransformation() { return transformation; } public void setTransformation(Transformation transformation) { this.transformation = transformation; } public ImportanceWeighting getImportanceWeighting() { return importanceWeighting; } public void setImportanceWeighting(ImportanceWeighting importanceWeighting) { this.importanceWeighting = importanceWeighting; } public ChangeLog getChangeLog() { return changeLog; } public void setChangeLog(ChangeLog value) { changeLog = value; } public boolean isChanged() { return changeLog.isAltered(); } public void touch() { changeLog.touch(); } /** * @see ITouchable#handleChanges(IChangesHandler) */ public void handleChanges(IChangesHandler h) { h.visit(this); // call handleChanges of all child elements alternativesDefinition.handleChanges(h); if (decision != null) decision.handleChanges(h); evaluation.handleChanges(h); importanceWeighting.handleChanges(h); projectBasis.handleChanges(h); planProperties.handleChanges(h); recommendation.handleChanges(h); requirementsDefinition.handleChanges(h); sampleRecordsDefinition.handleChanges(h); transformation.handleChanges(h); executablePlanDefinition.handleChanges(h); preservationActionPlan.handleChanges(h); planDefinition.handleChanges(h); tree.getRoot().handleChanges(h); } public RequirementsDefinition getRequirementsDefinition() { return requirementsDefinition; } public void setRequirementsDefinition(RequirementsDefinition requirementsDefinition) { this.requirementsDefinition = requirementsDefinition; } public Recommendation getRecommendation() { return recommendation; } public void setRecommendation(Recommendation recommendation) { this.recommendation = recommendation; } /** * removes a sample object and its associated result files and values * * @param record * SampleObject */ public void removeSampleObject(SampleObject record) { int index = getSampleRecordsDefinition().getRecords().indexOf(record); getSampleRecordsDefinition().removeRecord(record); getTree().removeValues(getAlternativesDefinition().getAlternatives(), index); // this SampleRecordsDefinition has been changed getSampleRecordsDefinition().touch(); for (Alternative alt : getAlternativesDefinition().getAlternatives()) { alt.getExperiment().getResults().remove(record); alt.getExperiment().getDetailedInfo().remove(record); } getAlternativesDefinition().touch(); } /** * Adds an alternative. * * @param alternative * Alternative to add. * @throws PlanningException * if an error at adding occurs (e.g. want to add an Alternative * with an already existing name). */ public void addAlternative(Alternative alternative) throws PlanningException { alternativesDefinition.addAlternative(alternative); alternativesDefinition.touch(); } /** * Removes an alternative AND also removes all associated evaluation values * contained in the tree, if there are any. * * @param alternative * alternative to remove */ public void removeAlternative(Alternative alternative) { if (recommendation.getAlternative() == alternative) { recommendation.setAlternative(null); recommendation.setReasoning(""); recommendation.setEffects(""); } alternativesDefinition.removeAlternative(alternative); alternativesDefinition.touch(); tree.removeValues(alternative); } /** * Method responsible for renaming an alternative (including all necessary * follow-up actions). * * @param alternative * Alternative to rename * @param newName * New name of the alternative * @throws PlanningException * This exception is thrown at any kind of problems at renaming. */ public void renameAlternative(Alternative alternative, String newName) throws PlanningException { // renaming only makes sense if the name really changed if (alternative.getName().equals(newName)) { return; } String oldName = alternative.getName(); // rename the alternative alternativesDefinition.renameAlternative(alternative, newName); // also update the alternative names in the tree tree.updateAlternativeName(oldName, newName); } /** * Sets the experiment workflow and removes associated evaluation values of * the alternative. * * @param alternative * alternative to modify * @param workflow * the workflow to set */ public void setExperimentWorkflow(Alternative alternative, DigitalObject workflow) { alternative.getExperiment().setWorkflow(workflow); alternative.getExperiment().touch(); // tree.removeValues(alternative); } /** * FIXME: are we really interested in the measures of detailedexperiment * info? why not either aggregate: - the measurements instead - the measures * mapped to decision criteria? * * @return */ public List<String> getMeasuredMeasures() { List<String> measures = new ArrayList<String>(); for (Alternative alternative : alternativesDefinition.getConsideredAlternatives()) { Experiment exp = alternative.getExperiment(); for (SampleObject record : sampleRecordsDefinition.getRecords()) { // is there any migration-metadata info? DetailedExperimentInfo info = exp.getDetailedInfo().get(record); if (info != null) { for (Measurement m : info.getMeasurements().values()) { if (!measures.contains(m.getMeasureId())) measures.add(m.getMeasureId()); } } } } Collections.sort(measures); return measures; } /** * sets primitive default values for all numeric and boolean transformers. * This is a minimalist approach for now, where we can plug in more * sophisticated heuristics in the future. Will on the other hand be less * necessary when we introduce property-specific transformers stored in the * knowledge base. */ public void calculateDefaultTransformers() { for (Leaf leaf : tree.getRoot().getAllLeaves()) { Transformer t = leaf.getTransformer(); if (t instanceof NumericTransformer) { // calculate min, max // set min,max NumericTransformer nt = (NumericTransformer) t; // A very specific assumption: the lower the better (!) // obviously often not true, e.g. for format/numberOfTools double min = Long.MAX_VALUE; double max = Long.MIN_VALUE; for (Alternative a : alternativesDefinition.getConsideredAlternatives()) { for (Value v : leaf.getValues(a.getName()).getList()) { INumericValue value = (INumericValue) v; if (value.value() > max) { max = value.value(); } if (value.value() < min) { min = value.value(); } } } nt.defaults(min, max); nt.setMode(TransformationMode.LINEAR); } else { OrdinalTransformer ot = (OrdinalTransformer) t; if (leaf.getScale() instanceof BooleanScale) { ot.getMapping().put("Yes", new TargetValueObject(5)); ot.getMapping().put("No", new TargetValueObject(1)); } else { // total nonsense placeholder for setting something // until we have proper heuristics and property-specific // transformers in the knowledge base for (String s : ot.getMapping().keySet()) { ot.getMapping().put(s, new TargetValueObject(3.33)); } } } t.touch(); } } /** * Returns the acceptable alternatives of a plan. Acceptable alternatives * are alternatives which contain no knock-out(0 evaluation). * * @return List of acceptable alternatives. */ public List<Alternative> getAcceptableAlternatives() { List<Alternative> acceptableAlternatives = new ArrayList<Alternative>(); ResultNode multNode = new ResultNode(getTree().getRoot(), new WeightedMultiplication(), getAlternativesDefinition().getConsideredAlternatives()); for (Alternative a : getAlternativesDefinition().getConsideredAlternatives()) { Double alternativeResult = multNode.getResults().get(a.getName()); if (alternativeResult > 0.0) { acceptableAlternatives.add(a); } } return acceptableAlternatives; } /** * Returns a list of all digital objects in the plan. * * @return a list of digital objects */ public List<DigitalObject> getDigitalObjects() { List<DigitalObject> list = new ArrayList<DigitalObject>(); list.add(getPlanProperties().getReportUpload()); list.addAll(getSampleRecordsDefinition().getRecords()); list.addAll(getRequirementsDefinition().getUploads()); for (Alternative a : getAlternativesDefinition().getAlternatives()) { if (a.getExperiment().getWorkflow() != null) { list.add(a.getExperiment().getWorkflow()); } for (DigitalObject r : a.getExperiment().getResults().values()) { list.add(r); } } if (getExecutablePlanDefinition().getT2flowExecutablePlan() != null) { list.add(getExecutablePlanDefinition().getT2flowExecutablePlan()); } if (getPreservationActionPlan() != null) { list.add(getPreservationActionPlan()); } if (getSampleRecordsDefinition().getCollectionProfile() != null && getSampleRecordsDefinition().getCollectionProfile().getProfile() != null) { list.add(getSampleRecordsDefinition().getCollectionProfile().getProfile()); } List<DigitalObject> xcdlObjects = new ArrayList<DigitalObject>(); for (DigitalObject object : list) { if (object.getXcdlDescription() != null) { xcdlObjects.add(object.getXcdlDescription()); } } list.addAll(xcdlObjects); return list; } /** * Method responsible for checking if the given alternative is the current * recommended one. * * @param alternative * Alternative to check. * @return True if the given alternative is the current recommended one. * False otherwise. */ public boolean isGivenAlternativeTheCurrentRecommendation(Alternative alternative) { if (recommendation.getAlternative() == alternative) { return true; } return false; } public boolean isApproved() { return (getPlanProperties().getState() == PlanState.PLAN_VALIDATED); } /** * Method responsible for initializing not yet initialized experiment infos. */ public void initializeExperimentInfos() { for (Alternative alternative : alternativesDefinition.getAlternatives()) { for (SampleObject sampleObject : sampleRecordsDefinition.getRecords()) { DetailedExperimentInfo experimentInfo = alternative.getExperiment().getDetailedInfo().get(sampleObject); if (experimentInfo == null) { experimentInfo = new DetailedExperimentInfo(); alternative.getExperiment().getDetailedInfo().put(sampleObject, experimentInfo); } if (experimentInfo.getProgramOutput() == null) { experimentInfo.setProgramOutput(""); } } } } public boolean isReadOnly() { return readOnly; } public void setReadOnly(boolean readOnly) { this.readOnly = readOnly; } }