eu.planets_project.pp.plato.xml.ProjectExporter.java Source code

Java tutorial

Introduction

Here is the source code for eu.planets_project.pp.plato.xml.ProjectExporter.java

Source

/*******************************************************************************
 * 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.xml;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.Map;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Namespace;
import org.dom4j.QName;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;

import sun.misc.BASE64Encoder;
import eu.planets_project.pp.plato.model.Alternative;
import eu.planets_project.pp.plato.model.ChangeLog;
import eu.planets_project.pp.plato.model.DetailedExperimentInfo;
import eu.planets_project.pp.plato.model.DigitalObject;
import eu.planets_project.pp.plato.model.ExecutablePlanDefinition;
import eu.planets_project.pp.plato.model.Experiment;
import eu.planets_project.pp.plato.model.Parameter;
import eu.planets_project.pp.plato.model.Plan;
import eu.planets_project.pp.plato.model.PlanDefinition;
import eu.planets_project.pp.plato.model.Policy;
import eu.planets_project.pp.plato.model.PolicyNode;
import eu.planets_project.pp.plato.model.SampleObject;
import eu.planets_project.pp.plato.model.TargetValueObject;
import eu.planets_project.pp.plato.model.Trigger;
import eu.planets_project.pp.plato.model.measurement.MeasurableProperty;
import eu.planets_project.pp.plato.model.measurement.Measurement;
import eu.planets_project.pp.plato.model.measurement.MeasurementInfo;
import eu.planets_project.pp.plato.model.measurement.Metric;
import eu.planets_project.pp.plato.model.scales.FreeStringScale;
import eu.planets_project.pp.plato.model.scales.RestrictedScale;
import eu.planets_project.pp.plato.model.scales.Scale;
import eu.planets_project.pp.plato.model.scales.ScaleType;
import eu.planets_project.pp.plato.model.transform.NumericTransformer;
import eu.planets_project.pp.plato.model.transform.OrdinalTransformer;
import eu.planets_project.pp.plato.model.tree.Leaf;
import eu.planets_project.pp.plato.model.tree.TemplateTree;
import eu.planets_project.pp.plato.model.tree.TreeNode;
import eu.planets_project.pp.plato.model.values.Value;
import eu.planets_project.pp.plato.util.FloatFormatter;
import eu.planets_project.pp.plato.util.PlatoLogger;

/**
 * Static methods providing means to export projects to XML using dom4j.
 *
 * @author Michael Kraxner
 */
public class ProjectExporter {

    private TimestampFormatter formatter = new TimestampFormatter();
    private FloatFormatter floatFormatter = new FloatFormatter();

    public static OutputFormat prettyFormat = new OutputFormat(" ", true, "UTF-8"); //OutputFormat.createPrettyPrint();
    public static OutputFormat compactFormat = new OutputFormat(null, false, "UTF-8"); //OutputFormat.createPrettyPrint();

    public static Namespace excutablePlanNS;
    public static Namespace xsi;
    public static Namespace platoNS;

    static {
        excutablePlanNS = new Namespace("wdt", "http://www.planets-project.eu/wdt");
        xsi = new Namespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
        platoNS = new Namespace("", "http://www.planets-project.eu/plato");
    }

    public ProjectExporter() {

    }

    /**
     * Returns the xml-representation of the given project as a String.
     * NOTE: It writes all data - including encoded binary data - directly to the DOM tree
     *       this may result in performance problems for large amounts of data.  
     */
    public String exportToString(Plan p) {
        return exportToXml(p).asXML();
    }

    /**
     * Takes the given project and turns it into a dom4j-xml-representation.
     * NOTE: It writes all data - including encoded binary data - directly to the DOM tree
     *       this may result in performance problems for large amounts of data.  
     * @return dom4j-document representing the given project
     */
    public Document exportToXml(Plan p) {
        Document doc = createProjectDoc();
        addProject(p, doc, null, null);
        return doc;
    }

    public Document createProjectDoc() {
        Document doc = DocumentHelper.createDocument();

        Element root = doc.addElement("plans");

        root.add(xsi);
        root.add(platoNS);
        root.addAttribute(xsi.getPrefix() + ":schemaLocation", "http://www.planets-project.eu/plato plato-3.0.xsd");

        root.add(excutablePlanNS);
        root.add(new Namespace("fits", "http://hul.harvard.edu/ois/xml/ns/fits/fits_output"));

        // set version of corresponding schema
        root.addAttribute("version", "3.0.0");

        return doc;
    }

    public Document createTemplateDoc() {
        Document doc = DocumentHelper.createDocument();

        Element root = doc.addElement("templates");

        root.add(xsi);

        return doc;
    }

    /**
     * This method creates a template xml from the objective tree contained in Plan p. The template xml is of the form
     * 
     * <templates>
     *   <template name="Public Fragments">
     *     <node name="Template 1" weight="0.0" single="false" lock="false">
     *         <node name="Interactive multimedia presentations" weight="0.0" single="false" lock="false">
     *      ...
     *   </template>
     * </templates>
     * 
     * For creating the tree we use {@link ProjectExporter#addSubTree(TreeNode, Element)} and remove afterwards
     * the evaluation part which we don't want to have in the template.
     *
     * @param p preservation plan
     * @param templateLibrary name of the template library (e.g. 'Public Templates')
     * @param name name of the template
     * @param description description of the template
     * @return xml as string
     */
    public String getObjectiveTreeAsTemplate(Plan p, String templateLibrary, String name, String description) {
        Document templateDoc = createTemplateDoc();

        Element template = templateDoc.getRootElement().addElement("template");
        template.addAttribute("name", templateLibrary);

        // Objectivetree (including weights, evaluation values and transformers)
        if (p.getTree().getRoot() != null) {
            addSubTree(p.getTree().getRoot(), template);
        }

        // get the root node of the tree
        Element treeRootNode = (Element) template.selectSingleNode("//templates/template/node");

        // change the name of the template
        if (name == null) {
            name = "";
        }
        treeRootNode.addAttribute("name", name);

        if (description == null) {
            description = "";
        }
        // set the description of the template
        Element descriptionElement = treeRootNode.addElement("description");
        descriptionElement.setText(description);

        // remove the evaluation from the template
        List<Element> nodes = templateDoc.selectNodes("//leaf/evaluation");

        for (Element n : nodes) {
            n.getParent().remove(n);
        }

        return templateDoc.asXML();
    }

    /**
     * Takes the given projects and turns them into a dom4j-xml-representation.
     * NOTE: It writes all data - including encoded binary data - directly to the DOM tree
     *   this may result in performance problems for large amounts of data.  
     * @return dom4j-document representing the given project
    public static Document exportToXml(List<Plan> projects) {
    Document doc = createProjectDoc();
        
    for (Plan project : projects) {
        ProjectExporter.addProject(project, doc, null, null);
    }
    return doc;
    }
    */

    /**
     * Writes the xml-representation of the given projects to the given target file.
     * NOTE: It writes all data - including encoded binary data - directly to the DOM tree
     *       this may result in performance problems for large amounts of data.  
     */
    public void exportToFile(Plan p, File target) throws IOException {
        XMLWriter writer = new XMLWriter(new FileWriter(target), ProjectExporter.prettyFormat);
        writer.write(exportToXml(p));
        writer.close();
    }

    /**
     * Writes the xml-representation of the given project into a temporary file and
     * returns the java-representation of this file.
     * NOTE: It writes all data - including encoded binary data - directly to the DOM tree
     *       this may result in performance problems for large amounts of data.  
     * 
     */
    public File exportToFile(Plan p) throws IOException {
        File temp = File.createTempFile("plato-plan-export-", ".xml");
        exportToFile(p, temp);
        return temp;
    }

    /*    public static File exportTemplatesToFile(List<TemplateTree> trees) throws IOException {
    File temp = File.createTempFile("plato-templates-export-", ".xml");
    XMLWriter writer = new XMLWriter(new FileWriter(temp), ProjectExporter.prettyFormat);
    //writer.setMaximumAllowedCharacter(127);
    writer.write(ProjectExporter.exportTemplates(trees));
    writer.close();
    return temp;
        }*/

    public Document exportTemplates(List<TemplateTree> trees) {
        Document doc = DocumentHelper.createDocument();

        Element root = doc.addElement("templates");

        for (TemplateTree template : trees) {
            addTemplateTree(template, root);
        }
        return doc;
    }

    /**
     * Adds the XML representation of the complete template tree to the parent node <code>root</code>.
     *
     * @param tree
     * @param root
     */
    private void addTemplateTree(TemplateTree tree, Element root) {
        Element template = root.addElement("template");
        template.addAttribute("name", tree.getName());
        template.addAttribute("owner", tree.getOwner());
        for (TreeNode child : tree.getRoot().getChildren()) {
            addSubTree(child, template);
        }
    }

    private void addSubPolicyTree(PolicyNode data, Element xmlRoot) {

        if (data == null) {
            return;
        }

        if (data.isPolicy()) {
            Element policy = xmlRoot.addElement("policy");

            Policy p = (Policy) data;

            policy.addAttribute("name", p.getName());
            policy.addAttribute("value", p.getValue());
        } else {
            Element policyNode = xmlRoot.addElement("policyNode");

            policyNode.addAttribute("name", data.getName());

            for (PolicyNode pn : data.getChildren()) {
                addSubPolicyTree(pn, policyNode);
            }
        }
    }

    /**
     * Adds the XML-representation of the treenode <code>data</code> to the parent node <code>xmlRoot</code>.
     * (also called recursivly for its children)
     *
     * @param data
     * @param xmlRoot
     */
    private void addSubTree(TreeNode data, Element xmlRoot) {
        if (data.isLeaf()) {
            Element leaf = xmlRoot.addElement("leaf");
            addNodeAttributes(data, leaf);
            Leaf l = (Leaf) data;
            leaf.addElement("aggregationMode").addAttribute("value", l.getAggregationMode().name());

            String typename = null;
            /*
             * Scale:
             * A special element is created, depending on the type of the scale
             */
            Scale s = l.getScale();

            if (s != null) {
                typename = deriveElementname(s.getClass());

                addScale(s, leaf);

                // Transformer
                if (l.getTransformer() != null) {
                    Element transformer = leaf.addElement(deriveElementname(l.getTransformer().getClass()));

                    if (l.getTransformer() instanceof OrdinalTransformer) {

                        Map<String, TargetValueObject> mapping = ((OrdinalTransformer) l.getTransformer())
                                .getMapping();
                        Element mappings = transformer.addElement("mappings");
                        for (String ordinal : mapping.keySet()) {
                            mappings.addElement("mapping").addAttribute("ordinal", ordinal).addAttribute("target",
                                    floatFormatter.formatFloatPrecisly(mapping.get(ordinal).getValue()));
                        }
                    }
                    if (l.getTransformer() instanceof NumericTransformer) {
                        NumericTransformer nt = (NumericTransformer) l.getTransformer();
                        transformer.addElement("mode").addAttribute("value", nt.getMode().name());
                        Element thresholds = transformer.addElement("thresholds");
                        thresholds.addElement("threshold1")
                                .setText(floatFormatter.formatFloatPrecisly(nt.getThreshold1()));
                        thresholds.addElement("threshold2")
                                .setText(floatFormatter.formatFloatPrecisly(nt.getThreshold2()));
                        thresholds.addElement("threshold3")
                                .setText(floatFormatter.formatFloatPrecisly(nt.getThreshold3()));
                        thresholds.addElement("threshold4")
                                .setText(floatFormatter.formatFloatPrecisly(nt.getThreshold4()));
                        thresholds.addElement("threshold5")
                                .setText(floatFormatter.formatFloatPrecisly(nt.getThreshold5()));

                    }
                    addChangeLog(l.getTransformer().getChangeLog(), transformer);
                }

                if (l.getMeasurementInfo() != null) {
                    addMeasurementInfo(l.getMeasurementInfo(), leaf);
                }

                Element eval = leaf.addElement("evaluation");
                typename = typename.substring(0, typename.lastIndexOf("Scale"));
                /*
                 * keep in mind: there are only values of the considered alternatives in the map
                 */
                for (String a : l.getValueMap().keySet()) {
                    Element alt = eval.addElement("alternative");
                    alt.addAttribute("key", a);
                    addStringElement(alt, "comment", l.getValueMap().get(a).getComment());
                    for (Value v : l.getValueMap().get(a).getList()) {
                        /*
                         * A special element is created, depending on the type of the scale
                         */
                        Element valElement = alt.addElement(typename + "Result");
                        addStringElement(valElement, "value", v.toString());
                        addStringElement(valElement, "comment", v.getComment());
                        addChangeLog(v.getChangeLog(), valElement);

                    }
                }
            }

        } else { // not a leaf
            Element node = xmlRoot.addElement("node");
            addNodeAttributes(data, node);

            for (TreeNode child : data.getChildren()) {
                addSubTree(child, node);
            }
        }

    }

    /**
     * creates a new element with all information of the given measurementInfo,
     * and adds it to the parent node.
     * This includes the property and the chosen metric, but NOT the list of possible metrics. 
     *  
     * @param info
     * @param parent
     */
    private void addMeasurementInfo(MeasurementInfo info, Element parent) {
        Element infoEl = parent.addElement("measurementInfo");

        MeasurableProperty prop = (MeasurableProperty) info.getProperty();
        if (prop != null) {
            Element propertyEl = infoEl.addElement("property");
            addStringElement(propertyEl, "category", prop.getCategory().toString());
            addStringElement(propertyEl, "propertyId", prop.getPropertyId());
            addStringElement(propertyEl, "name", prop.getName());
            addStringElement(propertyEl, "description", prop.getDescription());
            addScale(prop.getScale(), propertyEl);
            // note: we only keep the selected property and metric
            addChangeLog(prop.getChangeLog(), propertyEl);

        }
        addMetric(info.getMetric(), infoEl);

        addChangeLog(info.getChangeLog(), infoEl);
    }

    /**
     * creates a new element with all information of the given metric, 
     * and adds it to the parent node.
     * 
     * @param m
     * @param parent
     */
    private void addMetric(Metric m, Element parent) {
        if (m != null) {
            Element metricEl = parent.addElement("metric");
            addStringElement(metricEl, "metricId", m.getMetricId());
            addStringElement(metricEl, "name", m.getName());
            addStringElement(metricEl, "description", m.getDescription());
            addScale(m.getScale(), metricEl);
            addChangeLog(m.getChangeLog(), metricEl);
        }
    }

    /**
     * Adds the given node's properties to the xmlNode.
     */
    private void addNodeAttributes(TreeNode data, Element xmlNode) {
        xmlNode.addAttribute("name", data.getName()).addAttribute("weight",
                floatFormatter.formatFloatPrecisly(data.getWeight()));
        if (data instanceof Leaf) {
            xmlNode.addAttribute("single", Boolean.toString(data.isSingle()));
        }
        xmlNode.addAttribute("lock", Boolean.toString(data.isLock()));

        addStringElement(xmlNode, "description", data.getDescription());

        addChangeLog(data.getChangeLog(), xmlNode);
    }

    private void addChangeLog(ChangeLog log, Element xmlNode) {
        if (log != null) {
            xmlNode.addElement("changelog").addAttribute("created", formatter.formatTimestamp(log.getCreated()))
                    .addAttribute("createdBy", log.getCreatedBy())
                    .addAttribute("changed", formatter.formatTimestamp(log.getChanged()))
                    .addAttribute("changedBy", log.getChangedBy());
        }
    }

    private Element addUpload(DigitalObject upload, Element parentNode, String elementName, BASE64Encoder encoder,
            List<Integer> uploadIDs) {
        Element xmlNode = null;
        if ((upload != null) && (!"".equals(upload.getFullname())) && upload.isDataExistent()) {
            xmlNode = parentNode.addElement(elementName);
            xmlNode.addAttribute("fullname", upload.getFullname()).addAttribute("contentType",
                    upload.getContentType());

            Element data = xmlNode.addElement("data");
            data.addAttribute("hasData", "true");
            data.addAttribute("encoding", "base64");
            if (uploadIDs != null) {
                // write only DigitalObject.id, it will be replaced later
                data.setText("" + upload.getId());
                // remember ID of upload
                uploadIDs.add(upload.getId());
            } else {
                // directly write encoded data
                data.setText(encoder.encode(upload.getData().getData()));
            }
            if (upload.getXcdlDescription() != null) {
                addUpload(upload.getXcdlDescription(), xmlNode, "xcdlDescription", encoder, uploadIDs);
            }
            addJhoveString(upload, encoder, xmlNode);
            addFitsInfo(upload, encoder, xmlNode);
            addChangeLog(upload.getChangeLog(), xmlNode);
        }
        return xmlNode;
    }

    private void addJhoveString(DigitalObject digitalObject, BASE64Encoder encoder, Element xmlNode) {
        String jhoveXML = digitalObject.getJhoveXMLString();
        if ((jhoveXML != null) && (!"".equals(jhoveXML))) {
            Element jhove = xmlNode.addElement("jhoveXML");
            jhove.addAttribute("encoding", "base64");
            try {
                jhove.setText(encoder.encode(jhoveXML.getBytes("UTF-8")));
            } catch (UnsupportedEncodingException e) {
                jhove.setText("");
                PlatoLogger.getLogger(this.getClass()).error(e.getMessage(), e);
            }
        }
    }

    private void addFitsInfo(DigitalObject digitalObject, BASE64Encoder encoder, Element xmlNode) {
        String fitsInfo = digitalObject.getFitsXMLString();
        if ((fitsInfo != null) && (!"".equals(fitsInfo))) {
            Element fitsElement = xmlNode.addElement("fitsXML");
            fitsElement.addAttribute("encoding", "base64");
            try {
                fitsElement.setText(encoder.encode(fitsInfo.getBytes("UTF-8")));
            } catch (UnsupportedEncodingException e) {
                fitsElement.setText("");
                PlatoLogger.getLogger(this.getClass()).error(e.getMessage(), e);
            }
        }
    }

    private String deriveElementname(Class c) {
        String name = c.getName();
        name = name.substring(name.lastIndexOf(".") + 1);
        name = name.substring(0, 1).toLowerCase() + name.substring(1);
        return name;
    }

    /**
     * Adds the XML-representation of the given project to the parent <code>projectNode</code>
     *
     * @param p
     * @param projectNode
     */
    public void addProject(Plan p, Document projectsDoc, List<Integer> uploadIDs, List<Integer> recordIDs) {

        Element projectNode = projectsDoc.getRootElement().addElement(new QName("plan", platoNS));

        // Base64 encoder for binary data
        BASE64Encoder encoder = new BASE64Encoder();

        addChangeLog(p.getChangeLog(), projectNode);

        // Plan state
        projectNode.addElement("state").addAttribute("value", Integer.toString(p.getState().getValue()));

        Element properties = projectNode.addElement("properties");
        addUpload(p.getPlanProperties().getReportUpload(), properties, "report", encoder, uploadIDs);

        // Plan properties
        properties.addAttribute("author", p.getPlanProperties().getAuthor())
                .addAttribute("organization", p.getPlanProperties().getOrganization())
                .addAttribute("name", p.getPlanProperties().getName())
                .addAttribute("privateProject", Boolean.toString(p.getPlanProperties().isPrivateProject()))
                .addAttribute("reportPublic", Boolean.toString(p.getPlanProperties().isReportPublic()));
        addStringElement(properties, "description", p.getPlanProperties().getDescription());
        addStringElement(properties, "owner", p.getPlanProperties().getOwner());
        addChangeLog(p.getPlanProperties().getChangeLog(), properties);

        // Plan Basis
        Element basis = projectNode.addElement("basis");
        basis.addAttribute("identificationCode", p.getProjectBasis().getIdentificationCode());

        addStringElement(basis, "documentTypes", p.getProjectBasis().getDocumentTypes());
        addStringElement(basis, "applyingPolicies", p.getProjectBasis().getApplyingPolicies());
        addStringElement(basis, "designatedCommunity", p.getProjectBasis().getDesignatedCommunity());
        addStringElement(basis, "mandate", p.getProjectBasis().getMandate());
        addStringElement(basis, "organisationalProcedures", p.getProjectBasis().getOrganisationalProcedures());
        addStringElement(basis, "planningPurpose", p.getProjectBasis().getPlanningPurpose());
        addStringElement(basis, "planRelations", p.getProjectBasis().getPlanRelations());
        addStringElement(basis, "preservationRights", p.getProjectBasis().getPreservationRights());
        addStringElement(basis, "referenceToAgreements", p.getProjectBasis().getReferenceToAgreements());

        Element triggers = basis.addElement("triggers");
        if (p.getProjectBasis().getTriggers() != null) {
            addTrigger(triggers, p.getProjectBasis().getTriggers().getNewCollection());
            addTrigger(triggers, p.getProjectBasis().getTriggers().getPeriodicReview());
            addTrigger(triggers, p.getProjectBasis().getTriggers().getChangedEnvironment());
            addTrigger(triggers, p.getProjectBasis().getTriggers().getChangedObjective());
            addTrigger(triggers, p.getProjectBasis().getTriggers().getChangedCollectionProfile());
        }

        Element policyTree = basis.addElement("policyTree");

        addSubPolicyTree(p.getProjectBasis().getPolicyTree().getRoot(), policyTree);

        addChangeLog(p.getProjectBasis().getChangeLog(), basis);

        // Sample Records
        Element samplerecords = projectNode.addElement("sampleRecords");
        addStringElement(samplerecords, "samplesDescription",
                p.getSampleRecordsDefinition().getSamplesDescription());

        Element collectionProfile = samplerecords.addElement("collectionProfile");
        if (p.getSampleRecordsDefinition().getCollectionProfile() != null) {
            addStringElement(collectionProfile, "collectionID",
                    p.getSampleRecordsDefinition().getCollectionProfile().getCollectionID());
            addStringElement(collectionProfile, "description",
                    p.getSampleRecordsDefinition().getCollectionProfile().getDescription());
            addStringElement(collectionProfile, "expectedGrowthRate",
                    p.getSampleRecordsDefinition().getCollectionProfile().getExpectedGrowthRate());
            addStringElement(collectionProfile, "numberOfObjects",
                    p.getSampleRecordsDefinition().getCollectionProfile().getNumberOfObjects());
            addStringElement(collectionProfile, "typeOfObjects",
                    p.getSampleRecordsDefinition().getCollectionProfile().getTypeOfObjects());
            addStringElement(collectionProfile, "retentionPeriod",
                    p.getSampleRecordsDefinition().getCollectionProfile().getRetentionPeriod());

            for (SampleObject rec : p.getSampleRecordsDefinition().getRecords()) {
                Element sampleRecord = samplerecords.addElement("record")
                        .addAttribute("shortName", rec.getShortName()).addAttribute("fullname", rec.getFullname())
                        .addAttribute("contentType", rec.getContentType());

                Element data = sampleRecord.addElement("data");
                if (rec.isDataExistent()) {
                    data.addAttribute("hasData", "true");
                    data.addAttribute("encoding", "base64");
                    if (recordIDs != null) {
                        data.setText("" + rec.getId());
                        recordIDs.add(rec.getId());
                    } else {
                        data.setText(encoder.encode(rec.getData().getData()));
                    }
                    addUpload(rec.getXcdlDescription(), sampleRecord, "xcdlDescription", encoder, uploadIDs);
                    addJhoveString(rec, encoder, sampleRecord);
                    addFitsInfo(rec, encoder, sampleRecord);
                } else {
                    data.addAttribute("hasData", "false");
                }

                Element formatInfo = sampleRecord.addElement("formatInfo")
                        .addAttribute("puid", rec.getFormatInfo().getPuid())
                        .addAttribute("name", rec.getFormatInfo().getName())
                        .addAttribute("version", rec.getFormatInfo().getVersion())
                        .addAttribute("mimeType", rec.getFormatInfo().getMimeType())
                        .addAttribute("defaultExtension", rec.getFormatInfo().getDefaultExtension());
                addChangeLog(rec.getFormatInfo().getChangeLog(), formatInfo);

                addChangeLog(rec.getChangeLog(), sampleRecord);

                addStringElement(sampleRecord, "description", rec.getDescription());
                addStringElement(sampleRecord, "originalTechnicalEnvironment",
                        rec.getOriginalTechnicalEnvironment());

            }
            addChangeLog(p.getSampleRecordsDefinition().getChangeLog(), samplerecords);

            // Requirementsdefinition
            Element rdef = projectNode.addElement("requirementsDefinition");
            addStringElement(rdef, "description", p.getRequirementsDefinition().getDescription());
            Element uploads = rdef.addElement("uploads");
            for (DigitalObject upload : p.getRequirementsDefinition().getUploads()) {
                addUpload(upload, uploads, "upload", encoder, uploadIDs);
            }
            addChangeLog(p.getRequirementsDefinition().getChangeLog(), rdef);

            // Alternatives
            Element alternatives = projectNode.addElement("alternatives");
            addStringElement(alternatives, "description", p.getAlternativesDefinition().getDescription());

            for (Alternative a : p.getAlternativesDefinition().getAlternatives()) {
                /*
                 * Export all alternatives (also discarded ones)
                 * Indices of the result-set reference only the considered alternatives!
                 */
                Element alt = alternatives.addElement("alternative")
                        .addAttribute("discarded", Boolean.toString(a.isDiscarded()))
                        .addAttribute("name", a.getName());
                addStringElement(alt, "description", a.getDescription());
                if (a.getAction() != null) {
                    Element action = alt.addElement("action");
                    action.addAttribute("shortname", a.getAction().getShortname())
                            .addAttribute("url", a.getAction().getUrl())
                            .addAttribute("actionIdentifier", a.getAction().getActionIdentifier())
                            .addAttribute("info", a.getAction().getInfo())
                            .addAttribute("targetFormat", a.getAction().getTargetFormat())
                            .addAttribute("executable", String.valueOf(a.getAction().isExecutable()));
                    addStringElement(action, "descriptor", a.getAction().getDescriptor());
                    addStringElement(action, "parameterInfo", a.getAction().getParameterInfo());

                    Element params = action.addElement("params");
                    if (a.getAction().getParams() != null) {
                        for (Parameter param : a.getAction().getParams()) {
                            params.addElement("param").addAttribute("name", param.getName()).addAttribute("value",
                                    param.getValue());
                        }
                    }
                    addChangeLog(a.getAction().getChangeLog(), action);
                }

                Element resourceDescr = alt.addElement("resourceDescription");
                addStringElement(resourceDescr, "necessaryResources",
                        a.getResourceDescription().getNecessaryResources());
                addStringElement(resourceDescr, "configSettings", a.getResourceDescription().getConfigSettings());
                addStringElement(resourceDescr, "reasonForConsidering",
                        a.getResourceDescription().getReasonForConsidering());
                addChangeLog(a.getResourceDescription().getChangeLog(), resourceDescr);

                Element experiment = alt.addElement("experiment");
                Experiment exp = a.getExperiment();
                addStringElement(experiment, "description", exp.getDescription());
                //addStringElement(experiment, "runDescription", exp.getRunDescription());
                addStringElement(experiment, "settings", exp.getSettings());
                uploads = experiment.addElement("results");
                for (SampleObject record : exp.getResults().keySet()) {
                    DigitalObject up = exp.getResults().get(record);
                    if (up != null) {
                        // only existing uploads are exported
                        Element upload = addUpload(up, uploads, "result", encoder, uploadIDs);
                        if (upload != null) {
                            upload.addAttribute("key", record.getShortName());
                        }
                    }
                }
                //            // */experiment/xcdlDescriptions/xcdlDescription
                //            Element xcdls = experiment.addElement("xcdlDescriptions");
                //            for (SampleObject record : exp.getResults().keySet()) {
                //                DigitalObject result = exp.getResults().get(record);
                //                if (result != null) {
                //                    XcdlDescription x = result.getXcdlDescription();
                //                    if (x != null) {
                //                        // only existing xcdls are exported
                //                        Element upload = addUpload(x, xcdls, "xcdlDescription", encoder, uploadIDs);
                //                        if (upload != null) {
                //                           upload.addAttribute("key", record.getShortName());
                //                        }
                //                    }
                //                }
                //            }
                // export detailed experiment info's
                Element detailedInfos = experiment.addElement("detailedInfos");
                for (SampleObject record : exp.getDetailedInfo().keySet()) {
                    DetailedExperimentInfo dinfo = exp.getDetailedInfo().get(record);
                    Element detailedInfo = detailedInfos.addElement("detailedInfo")
                            .addAttribute("key", record.getShortName())
                            .addAttribute("successful", "" + dinfo.getSuccessful());
                    addStringElement(detailedInfo, "programOutput", dinfo.getProgramOutput());
                    addStringElement(detailedInfo, "cpr", dinfo.getCpr());

                    Element measurements = detailedInfo.addElement("measurements");
                    for (Measurement m : dinfo.getMeasurements().values()) {
                        Element measurement = measurements.addElement("measurement");
                        // measurement.value:
                        String typename = deriveElementname(m.getValue().getClass());

                        Element valueElem = measurement.addElement(typename);
                        //.addAttribute("value", m.getValue().toString());
                        addStringElement(valueElem, "value", m.getValue().toString());
                        addChangeLog(m.getValue().getChangeLog(), valueElem);

                        // measurement.property:
                        Element property = measurement.addElement("property").addAttribute("name",
                                m.getProperty().getName());
                        addScale(m.getProperty().getScale(), property);
                    }
                }
                addChangeLog(a.getExperiment().getChangeLog(), experiment);

                addChangeLog(a.getChangeLog(), alt);
            }
            addChangeLog(p.getAlternativesDefinition().getChangeLog(), alternatives);

            // go-nogo - is created in the go-nogo step and need not exist
            if (p.getDecision() != null) {
                Element decision = projectNode.addElement("decision");
                addStringElement(decision, "reason", p.getDecision().getReason());
                addStringElement(decision, "actionNeeded", p.getDecision().getActionNeeded());
                decision.addElement("goDecision").addAttribute("value", p.getDecision().getDecision().name());
                addChangeLog(p.getDecision().getChangeLog(), decision);
            }
            // Evaluation
            Element evaluation = projectNode.addElement("evaluation");
            addStringElement(evaluation, "comment", p.getEvaluation().getComment());
            addChangeLog(p.getEvaluation().getChangeLog(), evaluation);

            // importance weighting
            Element importanceWeighting = projectNode.addElement("importanceWeighting");
            addStringElement(importanceWeighting, "comment", p.getImportanceWeighting().getComment());
            addChangeLog(p.getImportanceWeighting().getChangeLog(), importanceWeighting);

            // Recommendation
            Element recommendation = projectNode.addElement("recommendation");
            if (p.getRecommendation().getAlternative() != null) {
                recommendation.addAttribute("alternativeName", p.getRecommendation().getAlternative().getName());
            }
            addStringElement(recommendation, "reasoning", p.getRecommendation().getReasoning());
            addStringElement(recommendation, "effects", p.getRecommendation().getEffects());
            addChangeLog(p.getRecommendation().getChangeLog(), recommendation);

            // transformation
            Element trafo = projectNode.addElement("transformation");
            addStringElement(trafo, "comment", p.getTransformation().getComment());
            addChangeLog(p.getTransformation().getChangeLog(), trafo);

            // Objectivetree (including weights, evaluation values and transformers)
            Element tree = projectNode.addElement("tree");
            tree.addAttribute("weightsInitialized", "" + p.getTree().isWeightsInitialized());
            if (p.getTree().getRoot() != null)
                addSubTree(p.getTree().getRoot(), tree);
        }

        Element executablePlan = projectNode.addElement("executablePlan");

        try {
            if (p.getExecutablePlanDefinition().getExecutablePlan() != null) {
                Document execPlan = DocumentHelper.parseText(p.getExecutablePlanDefinition().getExecutablePlan());
                Element execPlanRoot = execPlan.getRootElement();
                if (execPlanRoot.hasContent()) {
                    Element planWorkflow = executablePlan.addElement("planWorkflow");
                    planWorkflow.add(execPlanRoot);
                }
            }

            if (p.getExecutablePlanDefinition().getEprintsExecutablePlan() != null) {
                Document execPlan = DocumentHelper
                        .parseText(p.getExecutablePlanDefinition().getEprintsExecutablePlan());
                Element execPlanRoot = execPlan.getRootElement();
                if (execPlanRoot.hasContent()) {
                    //Element planWorkflow = executablePlan.addElement("eprintsPlan");
                    executablePlan.add(execPlanRoot);
                }
            }

        } catch (DocumentException e) {
            // if the stored exec. plan is invalid for some reason, we leave the plan out.
            // TODO: HK this should no happen as we write the xml ourselves, but still, 
            // we need a mechanism here to prevent the export if the xml is invalid.
            PlatoLogger.getLogger(this.getClass()).error(e.getMessage(), e);
        }

        // TODO HK how does this here relate to the upper try-catch block and the exception??
        // Smells like a hack!
        ExecutablePlanDefinition plan = p.getExecutablePlanDefinition();
        addStringElement(executablePlan, "objectPath", plan.getObjectPath());
        addStringElement(executablePlan, "toolParameters", plan.getToolParameters());
        addStringElement(executablePlan, "triggersConditions", plan.getTriggersConditions());
        addStringElement(executablePlan, "validateQA", plan.getValidateQA());
        addChangeLog(plan.getChangeLog(), executablePlan);

        Element planDef = projectNode.addElement("planDefinition");
        PlanDefinition pdef = p.getPlanDefinition();
        planDef.addAttribute("currency", pdef.getCurrency());
        addStringElement(planDef, "costsIG", pdef.getCostsIG());
        addStringElement(planDef, "costsPA", pdef.getCostsPA());
        addStringElement(planDef, "costsPE", pdef.getCostsPE());
        addStringElement(planDef, "costsQA", pdef.getCostsQA());
        addStringElement(planDef, "costsREI", pdef.getCostsREI());
        addStringElement(planDef, "costsRemarks", pdef.getCostsRemarks());
        addStringElement(planDef, "costsRM", pdef.getCostsRM());
        addStringElement(planDef, "costsTCO", pdef.getCostsTCO());

        addStringElement(planDef, "responsibleExecution", pdef.getResponsibleExecution());
        addStringElement(planDef, "responsibleMonitoring", pdef.getResponsibleMonitoring());

        triggers = planDef.addElement("triggers");
        if (pdef.getTriggers() != null) {
            addTrigger(triggers, pdef.getTriggers().getNewCollection());
            addTrigger(triggers, pdef.getTriggers().getPeriodicReview());
            addTrigger(triggers, pdef.getTriggers().getChangedEnvironment());
            addTrigger(triggers, pdef.getTriggers().getChangedObjective());
            addTrigger(triggers, pdef.getTriggers().getChangedCollectionProfile());
        }

        addChangeLog(pdef.getChangeLog(), planDef);

    }

    private void addTrigger(Element triggers, Trigger t) {
        if (t == null) {
            return;
        }
        Element trigger = triggers.addElement("trigger");
        trigger.addAttribute("type", t.getType().name());
        trigger.addAttribute("active", Boolean.toString(t.isActive()));
        trigger.addAttribute("description", t.getDescription());
    }

    /**
     * Long strings are stored as XML-elements, not as attributes.
     * It is not possible to add an element with value <code>null</code>,
     * therefore this has to be handled here:
     * A new element is only added if there is a value at all.
     *
     * @param parent
     * @param name
     * @param value
     */
    private Element addStringElement(Element parent, String name, String value) {
        Element e = null;
        // &&(!"".equals(value)
        if (value != null) {
            e = parent.addElement(name);
            if (!"".equals(value)) {
                e.addText(value);
            }
        }
        return e;
    }

    public String exportTreeToFreemind(Plan plan) {
        return exportTreeToFreemind(plan.getTree().getRoot());
    }

    public String exportTreeToFreemind(TreeNode treeRoot) {
        Document doc = DocumentHelper.createDocument();
        Element root = doc.addElement("map");
        Namespace xsi = new Namespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");

        root.add(xsi);
        root.addAttribute("version", "0.8.1");

        root.addComment(
                "To view this file, download free mind mapping software FreeMind from http://freemind.sourceforge.net");
        addSubTreeFreemind(root, treeRoot);

        String xml = doc.asXML();
        //PlatoLogger.getLogger(ProjectExporter.class).debug(arg0)
        return xml;
    }

    private void addSubTreeFreemind(Element xmlElement, TreeNode node) {
        Element element = xmlElement.addElement("node");
        addFreemindAttributes(node, element);

        if (node.isLeaf()) {
            //add scale
            Leaf leaf = (Leaf) node;
            Scale scale = leaf.getScale();
            if (scale != null) {
                Element scaleElement = element.addElement("node");
                String restriction = "?";

                //restrictions: restrictedscale, ordinals -freestring

                if (scale instanceof FreeStringScale) {
                    restriction = "free text";
                } else if ((scale.getType() == ScaleType.ordinal || scale.getType() == ScaleType.restricted)
                        && !"".equals(((RestrictedScale) scale).getRestriction())) {
                    restriction = ((RestrictedScale) scale).getRestriction();
                } else {
                    restriction = scale.getUnit();
                }
                scaleElement.addAttribute("TEXT", restriction);
            }

        } else {
            // add children
            for (TreeNode child : node.getChildren()) {
                addSubTreeFreemind(element, child);
            }
        }
    }

    private void addFreemindAttributes(TreeNode node, Element element) {
        element.addAttribute("TEXT", node.getName());
        //TODO export weights? units? single? >> future.

        String mInfoUri = null;
        if (node.isLeaf()) {
            mInfoUri = ((Leaf) node).getMeasurementInfo().getUri();
        }
        // add DESCRIPTION if existent
        if (((mInfoUri != null) && (!"".equals(mInfoUri)))
                || (node.getDescription() != null && !"".equals(node.getDescription()))) {
            Element hook = element.addElement("hook");
            hook.addAttribute("NAME", "accessories/plugins/NodeNote.properties");
            Element description = hook.addElement("text");

            String descr = "";
            // and measurement info

            if ((mInfoUri != null) && (!"".equals(mInfoUri))) {
                descr = "measuredProperty=" + mInfoUri + "\n";
            }
            if (node.getDescription() != null) {
                descr = descr + node.getDescription();
            }
            description.setText(descr);
        }
    }

    private void addScale(Scale s, Element parent) {
        if (s != null) {
            String typename = deriveElementname(s.getClass());

            Element scale = parent.addElement(typename);
            //  && (!"".equals(s.getUnit()
            if (s.getUnit() != null) {
                scale.addAttribute("unit", s.getUnit());
            }
            //scale.addAttribute("displayName", s.getDisplayName());

            if (s instanceof RestrictedScale) {
                scale.addAttribute("restriction", ((RestrictedScale) s).getRestriction());
            }
            addChangeLog(s.getChangeLog(), scale);
        }
    }
}