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

Java tutorial

Introduction

Here is the source code for eu.planets_project.pp.plato.xml.ProjectImporter.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.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;

import javax.ejb.Remove;
import javax.ejb.Stateful;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.apache.commons.digester.CallMethodRule;
import org.apache.commons.digester.Digester;
import org.apache.commons.digester.NodeCreateRule;
import org.apache.commons.logging.Log;
import org.dom4j.io.XMLWriter;
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.Scope;
import org.xml.sax.SAXException;

import eu.planets_project.pp.plato.action.interfaces.IProjectImport;
import eu.planets_project.pp.plato.model.Alternative;
import eu.planets_project.pp.plato.model.AlternativesDefinition;
import eu.planets_project.pp.plato.model.CollectionProfile;
import eu.planets_project.pp.plato.model.Decision;
import eu.planets_project.pp.plato.model.DetailedExperimentInfo;
import eu.planets_project.pp.plato.model.DigitalObject;
import eu.planets_project.pp.plato.model.Evaluation;
import eu.planets_project.pp.plato.model.ExecutablePlanDefinition;
import eu.planets_project.pp.plato.model.FormatInfo;
import eu.planets_project.pp.plato.model.ImportanceWeighting;
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.PlanProperties;
import eu.planets_project.pp.plato.model.PlanState;
import eu.planets_project.pp.plato.model.Policy;
import eu.planets_project.pp.plato.model.PolicyNode;
import eu.planets_project.pp.plato.model.PreservationActionDefinition;
import eu.planets_project.pp.plato.model.ProjectBasis;
import eu.planets_project.pp.plato.model.RequirementsDefinition;
import eu.planets_project.pp.plato.model.ResourceDescription;
import eu.planets_project.pp.plato.model.SampleObject;
import eu.planets_project.pp.plato.model.SampleRecordsDefinition;
import eu.planets_project.pp.plato.model.Transformation;
import eu.planets_project.pp.plato.model.TriggerDefinition;
import eu.planets_project.pp.plato.model.Values;
import eu.planets_project.pp.plato.model.XcdlDescription;
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.BooleanScale;
import eu.planets_project.pp.plato.model.scales.FloatRangeScale;
import eu.planets_project.pp.plato.model.scales.FloatScale;
import eu.planets_project.pp.plato.model.scales.FreeStringScale;
import eu.planets_project.pp.plato.model.scales.IntRangeScale;
import eu.planets_project.pp.plato.model.scales.IntegerScale;
import eu.planets_project.pp.plato.model.scales.OrdinalScale;
import eu.planets_project.pp.plato.model.scales.PositiveFloatScale;
import eu.planets_project.pp.plato.model.scales.PositiveIntegerScale;
import eu.planets_project.pp.plato.model.scales.YanScale;
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.Node;
import eu.planets_project.pp.plato.model.tree.ObjectiveTree;
import eu.planets_project.pp.plato.model.tree.PolicyTree;
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.BooleanValue;
import eu.planets_project.pp.plato.model.values.FloatRangeValue;
import eu.planets_project.pp.plato.model.values.FloatValue;
import eu.planets_project.pp.plato.model.values.FreeStringValue;
import eu.planets_project.pp.plato.model.values.IntRangeValue;
import eu.planets_project.pp.plato.model.values.IntegerValue;
import eu.planets_project.pp.plato.model.values.OrdinalValue;
import eu.planets_project.pp.plato.model.values.PositiveFloatValue;
import eu.planets_project.pp.plato.model.values.PositiveIntegerValue;
import eu.planets_project.pp.plato.model.values.YanValue;
import eu.planets_project.pp.plato.services.characterisation.jhove.JHoveAdaptor;
import eu.planets_project.pp.plato.util.FileUtils;
import eu.planets_project.pp.plato.util.OS;
import eu.planets_project.pp.plato.util.PlatoLogger;
import eu.planets_project.pp.plato.xml.plato.BinaryDataWrapper;
import eu.planets_project.pp.plato.xml.plato.ChangeLogFactory;
import eu.planets_project.pp.plato.xml.plato.ExperimentWrapper;
import eu.planets_project.pp.plato.xml.plato.GoDecisionFactory;
import eu.planets_project.pp.plato.xml.plato.NodeContentWrapper;
import eu.planets_project.pp.plato.xml.plato.OrdinalTransformerMappingFactory;
import eu.planets_project.pp.plato.xml.plato.RecommendationWrapper;
import eu.planets_project.pp.plato.xml.plato.SampleAggregationModeFactory;
import eu.planets_project.pp.plato.xml.plato.TransformationModeFactory;
import eu.planets_project.pp.plato.xml.plato.TriggerFactory;

@Stateful
@Scope(ScopeType.SESSION)
@Name("projectImport")
@Cache(org.jboss.ejb3.cache.NoPassivationCache.class)
public class ProjectImporter implements IProjectImport, Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = 4909854324732570490L;

    private static Log log = PlatoLogger.getLogger(ProjectImporter.class);

    /**
     * Imports the given file. This function is only used for test purposes in
     * {@link #main(String[])}
     */
    public List<Plan> importProjects(String file) throws IOException, SAXException {
        FileInputStream in = new FileInputStream(file);
        return importProjects(in);
    }

    public int importAllProjectsFromDir(File dir) {
        int count = 0;
        for (String s : dir.list()) {
            log.debug("importing file: " + s);
            try {
                String file = dir.getAbsolutePath() + File.separator + s;
                for (Plan p : importProjects(file)) {
                    em.persist(p);
                    count++;
                }
            } catch (IOException e) {
                log.error("IMPORT FAILED: could not import file " + s, e);
            } catch (SAXException e) {
                log.error("IMPORT FAILED: could not import file " + s, e);
            }
        }
        return count;
    }

    private List<Plan> plans;

    private List<TemplateTree> templates;

    private String fileVersion;

    private List<String> appliedTransformations;

    @In
    EntityManager em;

    /**
     * can be used to set data for a specific ByteStream, for example for
     * two-pass XML import. but: NOT USED AT THE MOMENT, and probably will not
     * be needed soon!
     * 
     * @param byteStreamID
     * @param data
     *            private void insertData(int byteStreamID, byte[] data) {
     *            ByteStream b = em.find(ByteStream.class, byteStreamID); if (b
     *            == null) {
     *            log.error("INCONSISTENCY: bytestream with ID "+byteStreamID
     *            +" not found!"); return; } b.setData(data); em.persist(b);
     *            em.flush(); b = null; em.clear(); }
     */

    /**
     * Used by the digester every time a project has been parsed.
     * 
     * @param p
     */
    public void setProject(Plan p) {
        plans.add(p);
    }

    /**
     * Used by the digester every time a template has been parsed.
     * 
     * @param t
     */
    public void setTemplate(TemplateTree t) {
        templates.add(t);
    }

    @Destroy
    @Remove
    public void destroy() {

    }

    /**
     * Detect the version of the given XML representation of plans. If the
     * version of the XML representation is not up to date, necessary
     * transformations are applied.
     * 
     * @param importData
     * @return null if the transformation fails, otherwise an up to date XML
     *         representation
     * @throws IOException
     *             if parsing the XML representation fails
     * @throws SAXException
     *             if parsing the XML representation fails
     */
    public String getCurrentVersionData(InputStream in, String tempPath) throws IOException, SAXException {
        appliedTransformations = new ArrayList<String>();

        String originalFile = tempPath + "_original.xml";
        OutputStream out = new BufferedOutputStream(new FileOutputStream(originalFile));
        FileUtils.writeToFile(in, out);
        out.close();

        /** check for the version of the file **/
        fileVersion = "xxx";
        Digester d = new Digester();
        d.setValidating(false);
        StrictErrorHandler errorHandler = new StrictErrorHandler();
        d.setErrorHandler(errorHandler);
        d.push(this);
        // to read the version we have to support all versions:
        d.addSetProperties("*/projects", "version", "fileVersion");
        // manually migrated projects may have the file version in the node
        // projects/project
        d.addSetProperties("*/projects/project", "version", "fileVersion");
        // pre V1.3 version info was stored in the project node
        d.addSetProperties("*/project", "version", "fileVersion");
        // since V1.9 the root node is plans:
        d.addSetProperties("*/plans", "version", "fileVersion");

        InputStream inV = new FileInputStream(originalFile);
        d.parse(inV);
        inV.close();
        /** this could be more sophisticated, but for now this is enough **/
        String version = "1.0";
        if (fileVersion != null) {
            version = fileVersion;
        }

        String fileTo = originalFile;
        String fileFrom = originalFile;

        boolean success = true;
        if ("xxx".equals(version)) {
            fileFrom = fileTo;
            fileTo = fileFrom + "_V1.3.xml";
            /** this is an old export file, transform it to the 1.3 schema **/
            success = transformXmlData(fileFrom, fileTo, "data/xslt/Vxxx-to-V1.3.xsl");
            appliedTransformations.add("Vxxx-to-V1.3.xsl");
            version = "1.3";
        }
        if (success && "1.3".equals(version)) {
            fileFrom = fileTo;
            fileTo = fileFrom + "_V1.9.xml";
            success = transformXmlData(fileFrom, fileTo, "data/xslt/V1.3-to-V1.9.xsl");
            appliedTransformations.add("V1.3-to-V1.9.xsl");
            version = "1.9";
        }
        // with release of Plato 2.0 and its schema ProjectExporter creates
        // documents with version 2.0
        if (success && "1.9".equals(version)) {
            version = "2.0";
        }
        if (success && "2.0".equals(version)) {
            // transform the document to version 2.1
            fileFrom = fileTo;
            fileTo = fileFrom + "_V2.1.xml";
            success = transformXmlData(fileFrom, fileTo, "data/xslt/V2.0-to-V2.1.xsl");
            appliedTransformations.add("V2.0-to-V2.1.xsl");
            version = "2.1";
        }
        if (success && "2.1".equals(version)) {
            // transform the document to version 2.1.2
            fileFrom = fileTo;
            fileTo = fileFrom + "_V2.1.2.xml";
            success = transformXmlData(fileFrom, fileTo, "data/xslt/V2.1-to-V2.1.2.xsl");
            appliedTransformations.add("V2.1-to-V2.1.2.xsl");
            version = "2.1.2";
        }
        if (success && "2.1.1".equals(version)) {
            // transform the document to version 2.1.2
            fileFrom = fileTo;
            fileTo = fileFrom + "_V2.1.2.xml";
            success = transformXmlData(fileFrom, fileTo, "data/xslt/V2.1.1-to-V2.1.2.xsl");
            appliedTransformations.add("V2.1.1-to-V2.1.2.xsl");
            version = "2.1.2";
        }

        if (success && "2.1.2".equals(version)) {
            // transform the document to version 3.0.0
            fileFrom = fileTo;
            fileTo = fileFrom + "_V3.0.0.xml";
            success = transformXmlData(fileFrom, fileTo, "data/xslt/V2.1.2-to-V3.0.0.xsl");
            appliedTransformations.add("V2.1.2-to-V3.0.0.xsl");
            version = "3.0.0";
        }

        if (success) {
            return fileTo;
        } else {
            return null;
        }
    }

    public boolean transformXmlData(String fromFile, String toFile, String xslFile) throws IOException {
        try {
            InputStream xsl = Thread.currentThread().getContextClassLoader().getResourceAsStream(xslFile);
            TransformerFactory transformerFactory = TransformerFactory.newInstance();
            Transformer transformer = transformerFactory.newTransformer(new StreamSource(xsl));// schemaUrl.openStream()

            OutputStream transformedOut = new FileOutputStream(toFile);
            Result outputTarget = new StreamResult(transformedOut);

            Source xmlSource = new StreamSource(new FileInputStream(fromFile));

            transformer.transform(xmlSource, outputTarget);
            transformedOut.close();
            return true;

        } catch (TransformerConfigurationException e) {
            log.debug(e);
        } catch (TransformerFactoryConfigurationError e) {
            log.debug(e);
        } catch (TransformerException e) {
            log.debug(e);
        }
        return false;

    }

    /**
     * This method takes a template xml and stores the templates in the template library. The 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>  
     *  
     *  We go through the templates //templates/template/node and store them in the respective template library, in this
     *  case 'Public Fragments'
     * 
     * @param xml
     * @throws SAXException
     * @throws IOException
     */
    public void storeTemplatesInLibrary(byte[] xml) throws SAXException, IOException {

        List<TemplateTree> templates = importTemplates(xml);
        /*
         * store all templates
         */
        for (TemplateTree template : templates) {

            // we get the template tree ("Public Templates") from the database 
            TemplateTree tdb;
            try {
                tdb = (TemplateTree) em.createQuery("select n from TemplateTree n where name = :name")
                        .setParameter("name", template.getName()).getSingleResult();
            } catch (NoResultException e) {
                tdb = new TemplateTree(template.getName(), null);
            }

            if (tdb != null) {

                // we get the templates and add them to the tree
                // and store them
                for (TreeNode n : template.getRoot().getChildren()) {
                    tdb.getRoot().addChild(n);
                    em.persist(n);
                }

                em.persist(em.merge(tdb));
                em.flush();

            }
        }
    }

    /**
     * Imports the XML representation of templates.
     * 
     * @return list of read templates.
     */
    public List<TemplateTree> importTemplates(byte[] in) throws IOException, SAXException {

        Digester digester = new Digester();
        //digester.setValidating(true);
        StrictErrorHandler errorHandler = new StrictErrorHandler();
        digester.setErrorHandler(errorHandler);

        // At the moment XML files for template tree's are only used internally, 
        // later we will define a schema and use it also for validation

        digester.push(this);

        digester.addObjectCreate("*/template", TemplateTree.class);
        digester.addSetProperties("*/template");
        digester.addSetRoot("*/template", "setTemplate");
        //        digester.addSetNext("*/template/name", "setName");
        //        digester.addSetNext("*/template/owner", "setOwner");

        ProjectImporter.addTreeParsingRulesToDigester(digester);

        digester.addObjectCreate("*/template/node", Node.class);
        digester.addSetProperties("*/template/node");
        digester.addSetNext("*/template/node", "addChild");

        digester.setUseContextClassLoader(true);

        this.templates = new ArrayList<TemplateTree>();
        digester.parse(new ByteArrayInputStream(in));
        /*
         * for (TemplateTree t : this.templates) { log.info(t.getName() +
         * t.getOwner()); }
         */
        return this.templates;
    }

    /**
     * Imports the XML representation of plans from the given inputstream.
     * 
     * @return list of read plans
     */
    public List<Plan> importProjects(InputStream in) throws IOException, SAXException {
        String tempPath = OS.getTmpPath() + "import_xml" + System.currentTimeMillis() + "/";
        File tempDir = new File(tempPath);
        tempDir.mkdirs();
        try {
            String currentVersionFile = getCurrentVersionData(in, tempPath);

            if (currentVersionFile == null) {
                log.error("Failed to migrate plans.");
                return this.plans;
            }

            Digester digester = new Digester();
            //            digester.setValidating(true);
            StrictErrorHandler errorHandler = new StrictErrorHandler();
            digester.setErrorHandler(errorHandler);
            digester.setNamespaceAware(true);
            //            digester.setSchemaLanguage("http://www.w3.org/2001/XMLSchema");
            //            digester.setSchema("http://localhost:8080/plato/schema/plato-2.1.xsd");

            /*
             * It is NOT sufficient to use setValidating(true) and digester.setSchema("data/schemas/plato.xsd")!
             * the following parameters have to be set and a special error handler is necessary
             */
            try {
                digester.setFeature("http://xml.org/sax/features/validation", true);
                digester.setFeature("http://apache.org/xml/features/validation/schema", true);
                //    
                digester.setFeature("http://xml.org/sax/features/namespaces", true);
                //                digester.setFeature("http://apache.org/xml/features/validation/schema-full-checking", true);
                /*
                 * And provide the relative path to the xsd-schema:
                 */
                digester.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage",
                        "http://www.w3.org/2001/XMLSchema");
                URL platoSchema = Thread.currentThread().getContextClassLoader()
                        .getResource("data/schemas/plato-3.0.xsd");
                URL wdtSchema = Thread.currentThread().getContextClassLoader()
                        .getResource("data/schemas/planets_wdt-1.0.xsd");
                digester.setProperty("http://apache.org/xml/properties/schema/external-schemaLocation",
                        "http://www.planets-project.eu/plato " + platoSchema + " http://www.planets-project.eu/wdt "
                                + wdtSchema);
                //http://localhost:8080/plato/schema/planets_wdt-1.0.xsd
            } catch (ParserConfigurationException e) {
                log.debug("Cannot import XML file: Configuration of parser failed.", e);
                throw new SAXException("Cannot import XML file: Configuration of parser failed.");
            }

            digester.push(this);
            // start with a new file
            digester.addObjectCreate("*/plan", Plan.class);
            digester.addSetProperties("*/plan");
            digester.addSetRoot("*/plan", "setProject");

            digester.addFactoryCreate("*/changelog", ChangeLogFactory.class);
            digester.addSetNext("*/changelog", "setChangeLog");

            digester.addObjectCreate("*/plan/state", PlanState.class);
            digester.addSetProperties("*/plan/state");
            digester.addSetNext("*/plan/state", "setState");

            digester.addObjectCreate("*/plan/properties", PlanProperties.class);
            digester.addSetProperties("*/plan/properties");
            digester.addSetNext("*/plan/properties", "setPlanProperties");
            digester.addCallMethod("*/plan/properties/description", "setDescription", 0);
            digester.addCallMethod("*/plan/properties/owner", "setOwner", 0);

            addCreateUpload(digester, "*/plan/properties/report", "setReportUpload", DigitalObject.class);

            digester.addObjectCreate("*/plan/basis", ProjectBasis.class);
            digester.addSetProperties("*/plan/basis");
            digester.addSetNext("*/plan/basis", "setProjectBasis");
            digester.addCallMethod("*/plan/basis/applyingPolicies", "setApplyingPolicies", 0);
            digester.addCallMethod("*/plan/basis/designatedCommunity", "setDesignatedCommunity", 0);
            digester.addCallMethod("*/plan/basis/mandate", "setMandate", 0);

            digester.addCallMethod("*/plan/basis/documentTypes", "setDocumentTypes", 0);
            digester.addCallMethod("*/plan/basis/identificationCode", "setIdentificationCode", 0);
            digester.addCallMethod("*/plan/basis/organisationalProcedures", "setOrganisationalProcedures", 0);
            digester.addCallMethod("*/plan/basis/planningPurpose", "setPlanningPurpose", 0);
            digester.addCallMethod("*/plan/basis/planRelations", "setPlanRelations", 0);
            digester.addCallMethod("*/plan/basis/preservationRights", "setPreservationRights", 0);
            digester.addCallMethod("*/plan/basis/referenceToAgreements", "setReferenceToAgreements", 0);

            // define common rule for triggers, for all */triggers/...!
            // also used for PlanDefinition
            digester.addObjectCreate("*/triggers", TriggerDefinition.class);
            digester.addSetNext("*/triggers", "setTriggers");
            // every time a */triggers/trigger is encountered:
            digester.addFactoryCreate("*/triggers/trigger", TriggerFactory.class);
            digester.addSetNext("*/triggers/trigger", "setTrigger");

            //
            // Policy Tree
            //
            digester.addObjectCreate("*/plan/basis/policyTree", PolicyTree.class);
            digester.addSetProperties("*/plan/basis/policyTree");
            digester.addSetNext("*/plan/basis/policyTree", "setPolicyTree");

            digester.addObjectCreate("*/plan/basis/policyTree/policyNode", PolicyNode.class);
            digester.addSetProperties("*/plan/basis/policyTree/policyNode");
            digester.addSetNext("*/plan/basis/policyTree/policyNode", "setRoot");

            digester.addObjectCreate("*/policyNode/policyNode", PolicyNode.class);
            digester.addSetProperties("*/policyNode/policyNode");
            digester.addSetNext("*/policyNode/policyNode", "addChild");

            digester.addObjectCreate("*/policyNode/policy", Policy.class);
            digester.addSetProperties("*/policyNode/policy");
            digester.addSetNext("*/policyNode/policy", "addChild");

            //
            // Sample Records
            //

            digester.addObjectCreate("*/plan/sampleRecords", SampleRecordsDefinition.class);
            digester.addSetProperties("*/plan/sampleRecords");
            digester.addSetNext("*/plan/sampleRecords", "setSampleRecordsDefinition");

            digester.addCallMethod("*/plan/sampleRecords/samplesDescription", "setSamplesDescription", 0);

            // - records
            digester.addObjectCreate("*/record", SampleObject.class);
            digester.addSetProperties("*/record");
            digester.addSetNext("*/record", "addRecord");

            digester.addCallMethod("*/record/description", "setDescription", 0);
            digester.addCallMethod("*/record/originalTechnicalEnvironment", "setOriginalTechnicalEnvironment", 0);

            digester.addObjectCreate("*/record/data", BinaryDataWrapper.class);
            digester.addSetTop("*/record/data", "setData");
            digester.addCallMethod("*/record/data", "setFromBase64Encoded", 0);

            // set up an general rule for all jhove strings!
            digester.addObjectCreate("*/jhoveXML", BinaryDataWrapper.class);
            digester.addSetTop("*/jhoveXML", "setString");
            digester.addCallMethod("*/jhoveXML", "setFromBase64Encoded", 0);
            digester.addCallMethod("*/jhoveXML", "setMethodName", 1, new String[] { "java.lang.String" });
            digester.addObjectParam("*/jhoveXML", 0, "setJhoveXMLString");

            // set up general rule for all fitsXMLs
            digester.addObjectCreate("*/fitsXML", BinaryDataWrapper.class);
            digester.addSetTop("*/fitsXML", "setString");
            digester.addCallMethod("*/fitsXML", "setFromBase64Encoded", 0);
            digester.addCallMethod("*/fitsXML", "setMethodName", 1, new String[] { "java.lang.String" });
            digester.addObjectParam("*/fitsXML", 0, "setFitsXMLString");

            digester.addObjectCreate("*/record/formatInfo", FormatInfo.class);
            digester.addSetProperties("*/record/formatInfo");
            digester.addSetNext("*/record/formatInfo", "setFormatInfo");

            addCreateUpload(digester, "*/record/xcdlDescription", "setXcdlDescription", XcdlDescription.class);

            // - collection profile
            digester.addObjectCreate("*/plan/sampleRecords/collectionProfile", CollectionProfile.class);
            digester.addSetProperties("*/plan/sampleRecords/collectionProfile");
            digester.addSetNext("*/plan/sampleRecords/collectionProfile", "setCollectionProfile");

            digester.addCallMethod("*/plan/sampleRecords/collectionProfile/collectionID", "setCollectionID", 0);
            digester.addCallMethod("*/plan/sampleRecords/collectionProfile/description", "setDescription", 0);
            digester.addCallMethod("*/plan/sampleRecords/collectionProfile/numberOfObjects", "setNumberOfObjects",
                    0);
            digester.addCallMethod("*/plan/sampleRecords/collectionProfile/typeOfObjects", "setTypeOfObjects", 0);
            digester.addCallMethod("*/plan/sampleRecords/collectionProfile/expectedGrowthRate",
                    "setExpectedGrowthRate", 0);
            digester.addCallMethod("*/plan/sampleRecords/collectionProfile/retentionPeriod", "setRetentionPeriod",
                    0);

            // requirements definition
            digester.addObjectCreate("*/plan/requirementsDefinition", RequirementsDefinition.class);
            digester.addSetProperties("*/plan/requirementsDefinition");
            digester.addSetNext("*/plan/requirementsDefinition", "setRequirementsDefinition");

            digester.addCallMethod("*/plan/requirementsDefinition/description", "setDescription", 0);

            // - uploads
            digester.addObjectCreate("*/plan/requirementsDefinition/uploads", ArrayList.class);
            digester.addSetNext("*/plan/requirementsDefinition/uploads", "setUploads");
            addCreateUpload(digester, "*/plan/requirementsDefinition/uploads/upload", "add", DigitalObject.class);

            // alternatives
            digester.addObjectCreate("*/plan/alternatives", AlternativesDefinition.class);
            digester.addSetProperties("*/plan/alternatives");
            digester.addCallMethod("*/plan/alternatives/description", "setDescription", 0);
            digester.addSetNext("*/plan/alternatives", "setAlternativesDefinition");

            digester.addObjectCreate("*/plan/alternatives/alternative", Alternative.class);
            digester.addSetProperties("*/plan/alternatives/alternative");
            digester.addSetNext("*/plan/alternatives/alternative", "addAlternative");
            // - action
            digester.addObjectCreate("*/plan/alternatives/alternative/action", PreservationActionDefinition.class);
            digester.addSetProperties("*/plan/alternatives/alternative/action");
            digester.addBeanPropertySetter("*/plan/alternatives/alternative/action/descriptor");
            digester.addBeanPropertySetter("*/plan/alternatives/alternative/action/parameterInfo");

            digester.addSetNext("*/plan/alternatives/alternative/action", "setAction");

            digester.addCallMethod("*/plan/alternatives/alternative/description", "setDescription", 0);

            // - - params
            digester.addObjectCreate("*/plan/alternatives/alternative/action/params", LinkedList.class);
            digester.addSetNext("*/plan/alternatives/alternative/action/params", "setParams");

            digester.addObjectCreate("*/plan/alternatives/alternative/action/params/param", Parameter.class);
            digester.addSetProperties("*/plan/alternatives/alternative/action/params/param");
            digester.addSetNext("*/plan/alternatives/alternative/action/params/param", "add");
            // - resource description
            digester.addObjectCreate("*/resourceDescription", ResourceDescription.class);
            digester.addSetProperties("*/resourceDescription");
            digester.addSetNext("*/resourceDescription", "setResourceDescription");

            digester.addCallMethod("*/resourceDescription/configSettings", "setConfigSettings", 0);
            digester.addCallMethod("*/resourceDescription/necessaryResources", "setNecessaryResources", 0);
            digester.addCallMethod("*/resourceDescription/reasonForConsidering", "setReasonForConsidering", 0);

            // - experiment
            digester.addObjectCreate("*/experiment", ExperimentWrapper.class);
            digester.addSetProperties("*/experiment");
            digester.addSetNext("*/experiment", "setExperiment");
            digester.addCallMethod("*/experiment/description", "setDescription", 0);
            digester.addCallMethod("*/experiment/settings", "setSettings", 0);

            addCreateUpload(digester, "*/experiment/results/result", null, DigitalObject.class);
            addCreateUpload(digester, "*/result/xcdlDescription", "setXcdlDescription", XcdlDescription.class);

            // call function addUpload of ExperimentWrapper
            CallMethodRule r = new CallMethodRule(1, "addResult", 2); //method with two params
            // every time  */experiment/uploads/upload  is encountered
            digester.addRule("*/experiment/results/result", r);
            // use attribute "key" as first param
            digester.addCallParam("*/experiment/results/result", 0, "key");
            // and the object on stack (DigitalObject) as the second
            digester.addCallParam("*/experiment/results/result", 1, true);

            //        addCreateUpload(digester, "*/experiment/xcdlDescriptions/xcdlDescription", null, XcdlDescription.class);
            //        // call function addXcdlDescription of ExperimentWrapper
            //        r = new CallMethodRule(1, "addXcdlDescription", 2);  //method with two params
            //        // every time  */experiment/xcdlDescriptions/xcdlDescription  is encountered
            //        digester.addRule("*/experiment/xcdlDescriptions/xcdlDescription", r);
            //        // use attribute "key" as first param
            //        digester.addCallParam("*/experiment/xcdlDescriptions/xcdlDescription", 0 , "key");
            //        // and the object on stack (DigitalObject) as the second
            //        digester.addCallParam("*/experiment/xcdlDescriptions/xcdlDescription",1,true);

            digester.addObjectCreate("*/experiment/detailedInfos/detailedInfo", DetailedExperimentInfo.class);
            digester.addSetProperties("*/experiment/detailedInfos/detailedInfo");
            digester.addBeanPropertySetter("*/experiment/detailedInfos/detailedInfo/programOutput");
            digester.addBeanPropertySetter("*/experiment/detailedInfos/detailedInfo/cpr");

            // call function "addDetailedInfo" of ExperimentWrapper
            r = new CallMethodRule(1, "addDetailedInfo", 2); //method with two params
            // every time  */experiment/detailedInfos/detailedInfo is encountered
            digester.addRule("*/experiment/detailedInfos/detailedInfo", r);
            // use attribute "key" as first param
            digester.addCallParam("*/experiment/detailedInfos/detailedInfo", 0, "key");
            // and the object on stack as second parameter
            digester.addCallParam("*/experiment/detailedInfos/detailedInfo", 1, true);

            // read contained measurements:
            digester.addObjectCreate("*/detailedInfo/measurements/measurement", Measurement.class);
            digester.addSetNext("*/detailedInfo/measurements/measurement", "put");
            // values are defined with wild-cards, and therefore set automatically        
            digester.addObjectCreate("*/measurement/property", MeasurableProperty.class);
            digester.addSetProperties("*/measurement/property");
            digester.addSetNext("*/measurement/property", "setProperty");
            // scales are defined with wild-cards, and therefore set automatically 

            /*
             * for each value type a set of rules
             * because of FreeStringValue we need to store the value as XML-element
             * instead of an attribute
             * naming them "ResultValues" wasn't nice too
             */
            addCreateValue(digester, BooleanValue.class, "setValue");
            addCreateValue(digester, FloatRangeValue.class, "setValue");
            addCreateValue(digester, IntegerValue.class, "setValue");
            addCreateValue(digester, IntRangeValue.class, "setValue");
            addCreateValue(digester, OrdinalValue.class, "setValue");
            addCreateValue(digester, PositiveFloatValue.class, "setValue");
            addCreateValue(digester, PositiveIntegerValue.class, "setValue");
            addCreateValue(digester, YanValue.class, "setValue");
            addCreateValue(digester, FreeStringValue.class, "setValue");

            // go no go decision
            digester.addObjectCreate("*/plan/decision", Decision.class);
            digester.addSetProperties("*/plan/decision");
            digester.addSetNext("*/plan/decision", "setDecision");

            digester.addCallMethod("*/plan/decision/actionNeeded", "setActionNeeded", 0);
            digester.addCallMethod("*/plan/decision/reason", "setReason", 0);

            digester.addFactoryCreate("*/plan/decision/goDecision", GoDecisionFactory.class);
            digester.addSetNext("*/plan/decision/goDecision", "setDecision");

            // evaluation
            digester.addObjectCreate("*/plan/evaluation", Evaluation.class);
            digester.addSetProperties("*/plan/evaluation");
            digester.addSetNext("*/plan/evaluation", "setEvaluation");

            digester.addCallMethod("*/plan/evaluation/comment", "setComment", 0);

            // importance weighting
            digester.addObjectCreate("*/plan/importanceWeighting", ImportanceWeighting.class);
            digester.addSetProperties("*/plan/importanceWeighting");
            digester.addSetNext("*/plan/importanceWeighting", "setImportanceWeighting");

            digester.addCallMethod("*/plan/importanceWeighting/comment", "setComment", 0);

            // recommendation
            digester.addObjectCreate("*/plan/recommendation", RecommendationWrapper.class);
            digester.addSetProperties("*/plan/recommendation");
            digester.addSetNext("*/plan/recommendation", "setRecommendation");

            digester.addCallMethod("*/plan/recommendation/reasoning", "setReasoning", 0);
            digester.addCallMethod("*/plan/recommendation/effects", "setEffects", 0);

            // transformation
            digester.addObjectCreate("*/plan/transformation", Transformation.class);
            digester.addSetProperties("*/plan/transformation");
            digester.addSetNext("*/plan/transformation", "setTransformation");

            digester.addCallMethod("*/plan/transformation/comment", "setComment", 0);

            // Tree
            /* Some rules for tree parsing are necessary for importing templates too,
             * that's why they are added by this static method. */
            ProjectImporter.addTreeParsingRulesToDigester(digester);

            digester.addObjectCreate("*/leaf/evaluation", HashMap.class);
            digester.addSetNext("*/leaf/evaluation", "setValueMap");
            /*
             *  The valueMap has an entry for each (considered) alternative ...
             *  and for each alternative there is a list of values, one per SampleObject.
             *  Note: The digester uses a stack, therefore the rule to put the list of values to the valueMap
             *  must be added after the rule for adding the values to the list.
             */

            /*
             * 2. and for each alternative there is a list of values, one per SampleObject
             */
            digester.addObjectCreate("*/leaf/evaluation/alternative", Values.class);
            digester.addCallMethod("*/leaf/evaluation/alternative/comment", "setComment", 0);

            /*
             *  for each result-type a set of rules
             *  they are added to the valueMap by the rules above
             */
            addCreateResultValue(digester, BooleanValue.class);
            addCreateResultValue(digester, FloatValue.class);
            addCreateResultValue(digester, FloatRangeValue.class);
            addCreateResultValue(digester, IntegerValue.class);
            addCreateResultValue(digester, IntRangeValue.class);
            addCreateResultValue(digester, OrdinalValue.class);
            addCreateResultValue(digester, PositiveFloatValue.class);
            addCreateResultValue(digester, PositiveIntegerValue.class);
            addCreateResultValue(digester, YanValue.class);
            addCreateResultValue(digester, FreeStringValue.class);

            /*
             * 1. The valueMap has an entry for each (considered) alternative ...
             */
            // call put of the ValueMap (HashMap)
            r = new CallMethodRule(1, "put", 2);
            digester.addRule("*/leaf/evaluation/alternative", r);
            digester.addCallParam("*/leaf/evaluation/alternative", 0, "key");
            digester.addCallParam("*/leaf/evaluation/alternative", 1, true);

            //        digester.addObjectCreate("*/plan/executablePlan/planWorkflow", ExecutablePlanContentWrapper.class);
            //        digester.addSetProperties("*/plan/executablePlan/planWorkflow");
            //        digester.addSetNext("*/plan/executablePlan/planWorkflow", "setRecommendation");

            // Executable plan definition
            digester.addObjectCreate("*/plan/executablePlan", ExecutablePlanDefinition.class);
            digester.addSetProperties("*/plan/executablePlan");
            digester.addSetNext("*/plan/executablePlan", "setExecutablePlanDefinition");

            //
            // Import Planets executable plan if present
            //
            try {
                // object-create rules are called at the beginning element-tags, in the same order as defined 
                // first create the wrapper
                digester.addObjectCreate("*/plan/executablePlan/planWorkflow", NodeContentWrapper.class);
                // then an element for workflowConf
                digester.addRule("*/plan/executablePlan/planWorkflow/workflowConf", new NodeCreateRule());

                // CallMethod and SetNext rules are called at closing element-tags, (last in - first out!)

                CallMethodRule rr = new CallMethodRule(1, "setNodeContent", 2);
                digester.addRule("*/plan/executablePlan/planWorkflow/workflowConf", rr);
                // right below the wrapper is an instance of ExecutablePlanDefinition  
                digester.addCallParam("*/plan/executablePlan/planWorkflow/workflowConf", 0, 1);
                // provide the name of the setter method
                digester.addObjectParam("*/plan/executablePlan/planWorkflow/workflowConf", 1, "setExecutablePlan");

                // the generated node is not accessible as CallParam (why?!?), but available for addSetNext
                digester.addSetNext("*/plan/executablePlan/planWorkflow/workflowConf", "setNode");

            } catch (ParserConfigurationException e) {
                PlatoLogger.getLogger(this.getClass()).error(e.getMessage(), e);
            }

            //
            // Import EPrints executable plan if present
            //
            try {

                digester.addObjectCreate("*/plan/executablePlan/eprintsPlan", NodeContentWrapper.class);
                // then an element for workflowConf
                digester.addRule("*/plan/executablePlan/eprintsPlan", new NodeCreateRule());

                CallMethodRule rr2 = new CallMethodRule(1, "setNodeContentEPrintsPlan", 2);
                digester.addRule("*/plan/executablePlan/eprintsPlan", rr2);
                // right below the wrapper is an instance of ExecutablePlanDefinition  
                digester.addCallParam("*/plan/executablePlan/eprintsPlan", 0, 1);
                // provide the name of the setter method
                digester.addObjectParam("*/plan/executablePlan/eprintsPlan", 1, "setEprintsExecutablePlan");

                digester.addSetNext("*/plan/executablePlan/eprintsPlan", "setNode");

            } catch (ParserConfigurationException e) {
                PlatoLogger.getLogger(this.getClass()).error(e.getMessage(), e);
            }

            digester.addCallMethod("*/plan/executablePlan/objectPath", "setObjectPath", 0);
            digester.addCallMethod("*/plan/executablePlan/toolParameters", "setToolParameters", 0);
            digester.addCallMethod("*/plan/executablePlan/triggersConditions", "setTriggersConditions", 0);
            digester.addCallMethod("*/plan/executablePlan/validateQA", "setValidateQA", 0);

            // Plan definition        
            digester.addObjectCreate("*/plan/planDefinition", PlanDefinition.class);
            digester.addSetProperties("*/plan/planDefinition");
            digester.addSetNext("*/plan/planDefinition", "setPlanDefinition");

            digester.addCallMethod("*/plan/planDefinition/costsIG", "setCostsIG", 0);
            digester.addCallMethod("*/plan/planDefinition/costsPA", "setCostsPA", 0);
            digester.addCallMethod("*/plan/planDefinition/costsPE", "setCostsPE", 0);
            digester.addCallMethod("*/plan/planDefinition/costsQA", "setCostsQA", 0);
            digester.addCallMethod("*/plan/planDefinition/costsREI", "setCostsREI", 0);
            digester.addCallMethod("*/plan/planDefinition/costsRemarks", "setCostsRemarks", 0);
            digester.addCallMethod("*/plan/planDefinition/costsRM", "setCostsRM", 0);
            digester.addCallMethod("*/plan/planDefinition/costsTCO", "setCostsTCO", 0);
            digester.addCallMethod("*/plan/planDefinition/responsibleExecution", "setResponsibleExecution", 0);
            digester.addCallMethod("*/plan/planDefinition/responsibleMonitoring", "setResponsibleMonitoring", 0);

            digester.addObjectCreate("*/plan/planDefinition/triggers", TriggerDefinition.class);
            digester.addSetNext("*/plan/planDefinition/triggers", "setTriggers");
            // every time a */plan/basis/triggers/trigger is encountered:
            digester.addFactoryCreate("*/plan/planDefinition/triggers/trigger", TriggerFactory.class);
            digester.addSetNext("*/plan/planDefinition/triggers/trigger", "setTrigger");

            digester.setUseContextClassLoader(true);
            this.plans = new ArrayList<Plan>();

            // finally parse the XML representation with all created rules
            digester.parse(new FileInputStream(currentVersionFile));

            for (Plan plan : plans) {
                String projectName = plan.getPlanProperties().getName();
                if ((projectName != null) && (!"".equals(projectName))) {
                    /*
                     * establish links from values to scales
                     */
                    plan.getTree().initValues(plan.getAlternativesDefinition().getConsideredAlternatives(),
                            plan.getSampleRecordsDefinition().getRecords().size(), true);
                    /*
                     * establish references of Experiment.uploads
                     */
                    HashMap<String, SampleObject> records = new HashMap<String, SampleObject>();
                    for (SampleObject record : plan.getSampleRecordsDefinition().getRecords()) {
                        records.put(record.getShortName(), record);
                    }
                    for (Alternative alt : plan.getAlternativesDefinition().getAlternatives()) {
                        if ((alt.getExperiment() != null) && (alt.getExperiment() instanceof ExperimentWrapper)) {
                            alt.setExperiment(((ExperimentWrapper) alt.getExperiment()).getExperiment(records));
                        }
                    }

                    // DESCRIBE all DigitalObjects with Jhove.
                    for (SampleObject record : plan.getSampleRecordsDefinition().getRecords()) {
                        if (record.isDataExistent()) {
                            // characterise

                            try {
                                record.setJhoveXMLString(new JHoveAdaptor().describe(record));
                            } catch (Throwable e) {
                                log.error("Error running Jhove for record " + record.getShortName() + ". "
                                        + e.getMessage(), e);
                            }
                            for (Alternative alt : plan.getAlternativesDefinition().getAlternatives()) {
                                DigitalObject result = alt.getExperiment().getResults().get(record);
                                if (result != null && result.isDataExistent()) {
                                    try {
                                        result.setJhoveXMLString(new JHoveAdaptor().describe(result));
                                    } catch (Throwable e) {
                                        log.error(
                                                "Error running Jhove for record " + record.getShortName()
                                                        + ", alternative " + alt.getName() + ". " + e.getMessage(),
                                                e);
                                    }

                                }
                            }
                        }
                    }

                    // CHECK NUMERIC TRANSFORMER THRESHOLDS
                    for (Leaf l : plan.getTree().getRoot().getAllLeaves()) {
                        eu.planets_project.pp.plato.model.transform.Transformer t = l.getTransformer();
                        if (t != null && t instanceof NumericTransformer) {
                            NumericTransformer nt = (NumericTransformer) t;
                            if (!nt.checkOrder()) {
                                StringBuffer sb = new StringBuffer("NUMERICTRANSFORMER THRESHOLD ERROR ");
                                sb.append(l.getName()).append("::NUMERICTRANSFORMER:: ");
                                sb.append(nt.getThreshold1()).append(" ").append(nt.getThreshold2()).append(" ")
                                        .append(nt.getThreshold3()).append(" ").append(nt.getThreshold4())
                                        .append(" ").append(nt.getThreshold5());
                                log.error(sb.toString());
                            }
                        }
                    }

                    /*
                     * establish references to selected alternative
                     */
                    HashMap<String, Alternative> alternatives = new HashMap<String, Alternative>();
                    for (Alternative alt : plan.getAlternativesDefinition().getAlternatives()) {
                        alternatives.put(alt.getName(), alt);
                    }
                    if ((plan.getRecommendation() != null)
                            && (plan.getRecommendation() instanceof RecommendationWrapper)) {
                        plan.setRecommendation(
                                ((RecommendationWrapper) plan.getRecommendation()).getRecommendation(alternatives));
                    }
                    if ((plan.getState().getValue() == PlanState.ANALYSED) && ((plan.getRecommendation() == null)
                            || (plan.getRecommendation().getAlternative() == null))) {
                        /*
                         * This project is NOT completely analysed
                         */
                        plan.getState().setValue(PlanState.ANALYSED - 1);
                    }
                } else {
                    throw new SAXException("Could not find any project data.");
                }
            }
        } finally {
            OS.deleteDirectory(tempDir);
            /*
             * Importing big plans results in an increasing memory consumption
             * strange: The rise of memory consumption occurs when persisting the loaded project
             * NOT during parsing with the digester
             */
            System.gc();
        }

        return this.plans;
    }

    /**
     * 
     * @param args
     *            first entry is the filename of the xml-file to be imported
     *            second entry is the name of the output-file
     */
    public static void main(String[] args) {

        ProjectImporter projectImp = new ProjectImporter();
        ProjectExporter exporter = new ProjectExporter();
        try {
            for (Plan plan : projectImp.importProjects(args[0])) {
                System.out.println("Imported : " + plan.getPlanProperties().getName());
                // export the imported project
                FileOutputStream out = new FileOutputStream(args[1]);
                XMLWriter writer = new XMLWriter(out, ProjectExporter.prettyFormat);
                writer.write(exporter.exportToXml(plan));
                writer.close();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        }
    }

    /**
     * Create a rule for reading an upload entry for the given location
     * <code>pattern</code>, and use the <code>method</code> to set the upload
     * object.
     * 
     * @param digester
     * @param pattern
     * @param method
     */
    private static void addCreateUpload(Digester digester, String pattern, String method, Class objectType) {
        digester.addObjectCreate(pattern, objectType);
        digester.addSetProperties(pattern);
        if ((method != null) && (!"".equals(method)))
            digester.addSetNext(pattern, method);
        /*
         * Note: It is not possible to read element data, process it and pass it
         * to a function with a simple digester Rule, neither you can define a
         * factory to read the data of an element.
         * 
         * So we have to do it the other way round: (remember: the function
         * added last is executed first!)
         */
        // 1. Create a BinaryDataWrapper if a <data> element is encountered
        digester.addObjectCreate(pattern + "/data", BinaryDataWrapper.class);
        // 3. Finally call setData on the BinaryDataWrapper(!) on top with the
        // object next to top as argument
        // The BinaryDataWrapper will call setData on to object next to top with
        // the previously read and decoded data
        digester.addSetTop(pattern + "/data", "setData");
        // 2. Call setFromBase64Encoded on the BinaryDataWrapper to read the
        // elements content
        digester.addCallMethod(pattern + "/data", "setFromBase64Encoded", 0);

    }

    public void setFileVersion(String fileVersion) {
        this.fileVersion = fileVersion;
    }

    /**
     * This method adds rules for name, properties, scales, modes and mappings
     * only! Rules for importing measured values of alternatives are defined
     * seperately in importProjects()! (Refactored to its own method by Kevin)
     */
    private static void addTreeParsingRulesToDigester(Digester digester) {
        digester.addObjectCreate("*/plan/tree", ObjectiveTree.class);
        digester.addSetProperties("*/plan/tree");
        digester.addSetNext("*/plan/tree", "setTree");

        digester.addObjectCreate("*/node/node", Node.class);
        digester.addSetProperties("*/node/node");
        digester.addSetNext("*/node/node", "addChild");

        digester.addCallMethod("*/node/description", "setDescription", 0);

        digester.addObjectCreate("*/plan/tree/node", Node.class);
        digester.addSetProperties("*/plan/tree/node");
        digester.addSetNext("*/plan/tree/node", "setRoot");

        digester.addObjectCreate("*/leaf", Leaf.class);
        digester.addSetProperties("*/leaf");
        digester.addSetNext("*/leaf", "addChild");
        digester.addFactoryCreate("*/leaf/aggregationMode", SampleAggregationModeFactory.class);
        digester.addSetNext("*/leaf/aggregationMode", "setAggregationMode");

        digester.addCallMethod("*/leaf/description", "setDescription", 0);

        digester.addObjectCreate("*/measurementInfo", MeasurementInfo.class);
        digester.addSetNext("*/measurementInfo", "setMeasurementInfo");
        addPropertyRules(digester, "*/measurementInfo/property");
        // and the selected metric
        addMetricRules(digester, "*/measurementInfo/metric", "setMetric");

        /*
         * for each scale-type a set of rules
         */
        addCreateScale(digester, BooleanScale.class);
        addCreateScale(digester, FloatRangeScale.class);
        addCreateScale(digester, FloatScale.class);
        addCreateScale(digester, IntegerScale.class);
        addCreateScale(digester, IntRangeScale.class);
        addCreateScale(digester, OrdinalScale.class);
        addCreateScale(digester, PositiveFloatScale.class);
        addCreateScale(digester, PositiveIntegerScale.class);
        addCreateScale(digester, YanScale.class);
        addCreateScale(digester, FreeStringScale.class);

        /*
         * for each transformer type a set of rules
         */
        digester.addObjectCreate("*/leaf/numericTransformer", NumericTransformer.class);
        digester.addSetProperties("*/leaf/numericTransformer");
        digester.addFactoryCreate("*/leaf/numericTransformer/mode", TransformationModeFactory.class);
        digester.addSetNext("*/leaf/numericTransformer/mode", "setMode");
        digester.addBeanPropertySetter("*/leaf/numericTransformer/thresholds/threshold1", "threshold1");
        digester.addBeanPropertySetter("*/leaf/numericTransformer/thresholds/threshold2", "threshold2");
        digester.addBeanPropertySetter("*/leaf/numericTransformer/thresholds/threshold3", "threshold3");
        digester.addBeanPropertySetter("*/leaf/numericTransformer/thresholds/threshold4", "threshold4");
        digester.addBeanPropertySetter("*/leaf/numericTransformer/thresholds/threshold5", "threshold5");
        digester.addSetNext("*/leaf/numericTransformer", "setTransformer");

        // digester.addObjectCreate("*/numericTransformer/thresholds",
        // LinkedHashMap.class);
        // digester.addSetNext("*/numericTransformer/thresholds",
        // "setThresholds");
        // digester.addFactoryCreate("*/thresholds/threshold",
        // NumericTransformerThresholdFactory.class);

        digester.addObjectCreate("*/leaf/ordinalTransformer", OrdinalTransformer.class);
        digester.addSetProperties("*/leaf/ordinalTransformer");
        digester.addSetNext("*/leaf/ordinalTransformer", "setTransformer");

        digester.addObjectCreate("*/ordinalTransformer/mappings", LinkedHashMap.class);
        digester.addSetNext("*/ordinalTransformer/mappings", "setMapping");
        digester.addFactoryCreate("*/mappings/mapping", OrdinalTransformerMappingFactory.class);

        digester.addRule("*/mappings/mapping", new CallMethodRule(1, "put", 2)); // method
                                                                                 // with
                                                                                 // two
                                                                                 // params
        digester.addCallParam("*/mappings/mapping", 0, "ordinal"); // use
                                                                   // attribute
                                                                   // "ordinal"
                                                                   // as first
                                                                   // argument
        digester.addCallParam("*/mappings/mapping", 1, true); // and the object
                                                              // on the stack as
                                                              // second
    }

    private static void addCreateResultValue(Digester digester, Class c) {
        String name = c.getName();
        name = name.substring(name.lastIndexOf(".") + 1);
        name = name.substring(0, 1).toLowerCase() + name.substring(1);

        String pattern = "*/" + name.replace("Value", "Result");
        digester.addObjectCreate(pattern, c);
        digester.addSetProperties(pattern);
        digester.addBeanPropertySetter(pattern + "/value");
        digester.addBeanPropertySetter(pattern + "/comment");
        digester.addSetNext(pattern, "add");
    }

    private static void addCreateValue(Digester digester, Class c, String setNextMethod) {
        String name = c.getName();
        name = name.substring(name.lastIndexOf(".") + 1);
        name = name.substring(0, 1).toLowerCase() + name.substring(1);

        String pattern = "*/" + name;
        digester.addObjectCreate(pattern, c);
        // digester.addSetProperties(pattern);
        digester.addBeanPropertySetter(pattern + "/value");
        digester.addSetNext(pattern, setNextMethod);
    }

    private static void addCreateScale(Digester digester, Class c) {
        String name = c.getName();
        name = name.substring(name.lastIndexOf(".") + 1);
        name = name.substring(0, 1).toLowerCase() + name.substring(1);

        String pattern = "*/" + name;
        digester.addObjectCreate(pattern, c);
        digester.addSetProperties(pattern);
        digester.addSetNext(pattern, "setScale");
    }

    private static void addPropertyRules(Digester digester, String pattern) {
        digester.addObjectCreate(pattern, MeasurableProperty.class);
        digester.addSetNext(pattern, "setProperty");
        digester.addSetProperties(pattern);
        // there is no property categoryAsString, but a setter ...
        digester.addBeanPropertySetter(pattern + "/category", "categoryAsString");
        digester.addBeanPropertySetter(pattern + "/propertyId");
        digester.addBeanPropertySetter(pattern + "/name");
        digester.addBeanPropertySetter(pattern + "/description");

        // scale is created automatically with global rule
        //        digester.addObjectCreate(pattern + "/possibleMetrics", ArrayList.class);
        //        digester.addSetNext(pattern + "/possibleMetrics", "setPossibleMetrics");
        //        addMetricRules(digester, "*/possibleMetrics/metric", "add");

    }

    private static void addMetricRules(Digester digester, String pattern, String method) {
        digester.addObjectCreate(pattern, Metric.class);
        digester.addSetProperties(pattern);
        digester.addBeanPropertySetter(pattern + "/metricId");
        digester.addBeanPropertySetter(pattern + "/name");
        digester.addBeanPropertySetter(pattern + "/description");
        // scale is created automatically with "global" rule
        digester.addSetNext(pattern, method);
    }

    public List<String> getAppliedTransformations() {
        return appliedTransformations;
    }
}