eu.planets_project.tb.impl.services.mockups.workflow.MigrateWorkflow.java Source code

Java tutorial

Introduction

Here is the source code for eu.planets_project.tb.impl.services.mockups.workflow.MigrateWorkflow.java

Source

/*******************************************************************************
 * Copyright (c) 2007, 2010 The Planets Project Partners.
 *
 * 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.tb.impl.services.mockups.workflow;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Vector;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import eu.planets_project.ifr.core.techreg.formats.Format;
import eu.planets_project.ifr.core.techreg.formats.Format.UriType;
import eu.planets_project.ifr.core.techreg.formats.FormatRegistry;
import eu.planets_project.ifr.core.techreg.formats.FormatRegistryFactory;
import eu.planets_project.services.characterise.Characterise;
import eu.planets_project.services.characterise.CharacteriseResult;
import eu.planets_project.services.datatypes.Content;
import eu.planets_project.services.datatypes.DigitalObject;
import eu.planets_project.services.datatypes.Parameter;
import eu.planets_project.services.datatypes.Property;
import eu.planets_project.services.datatypes.ServiceReport;
import eu.planets_project.services.datatypes.ServiceReport.Status;
import eu.planets_project.services.datatypes.ServiceReport.Type;
import eu.planets_project.services.identify.Identify;
import eu.planets_project.services.identify.IdentifyResult;
import eu.planets_project.services.migrate.Migrate;
import eu.planets_project.services.migrate.MigrateResult;
import eu.planets_project.services.utils.DigitalObjectUtils;
import eu.planets_project.tb.gui.backing.ServiceBrowser;
import eu.planets_project.tb.gui.backing.exp.ExperimentStageBean;
import eu.planets_project.tb.impl.model.eval.mockup.TecRegMockup;
import eu.planets_project.tb.impl.model.exec.ExecutionStageRecordImpl;
import eu.planets_project.tb.impl.model.measure.MeasurementImpl;
import eu.planets_project.tb.impl.model.measure.MeasurementTarget;
import eu.planets_project.tb.impl.services.wrappers.CharacteriseWrapper;
import eu.planets_project.tb.impl.services.wrappers.IdentifyWrapper;
import eu.planets_project.tb.impl.services.wrappers.MigrateWrapper;

/**
 * This is the class that carries the code specific to invoking an Migrate experiment.
 * 
 * @author <a href="mailto:Andrew.Jackson@bl.uk">Andy Jackson</a>
 *
 */
public class MigrateWorkflow implements ExperimentWorkflow {
    private static Log log = LogFactory.getLog(MigrateWorkflow.class);

    /** External property keys */
    public static final String PARAM_SERVICE = "migrate.service";
    public static final String PARAM_FROM = "migrate.from";
    public static final String PARAM_TO = "migrate.to";
    public static final String PARAM_PRE_SERVICE = "migrate.pre.service";
    public static final String PARAM_PRE_SERVICE_TYPE = "migrate.pre.service.type";
    public static final String PARAM_POST_SERVICE = "migrate.post.service";
    public static final String PARAM_POST_SERVICE_TYPE = "migrate.post.service.type";
    public static final String SERVICE_TYPE_CHARACTERISE = "Characterise";
    public static final String SERVICE_TYPE_IDENTIFY = "Identify";

    /** Internal keys for easy referral to the service+stage combinations. */
    public static final String STAGE_PRE_MIGRATE = "Characterise Before Migration";
    public static final String STAGE_MIGRATE = "Migrate";
    public static final String STAGE_POST_MIGRATE = "Characterise After Migration";

    private static HashMap<String, List<MeasurementImpl>> manualObservables;
    /** Statically define the automatically observable properties. */
    private static HashMap<String, List<MeasurementImpl>> observables;
    static {
        observables = new HashMap<String, List<MeasurementImpl>>();
        observables.put(STAGE_MIGRATE, new Vector<MeasurementImpl>());
        // The service succeeded
        observables.get(STAGE_MIGRATE)
                .add(TecRegMockup.getObservable(TecRegMockup.PROP_SERVICE_EXECUTION_SUCEEDED));
        //FIXME What about the parameter: MIGRATE_SUCCESS, choosing enabled-ness...
        // The service time
        observables.get(STAGE_MIGRATE).add(TecRegMockup.getObservable(TecRegMockup.PROP_SERVICE_TIME));
        /*
        observables.put( MIGRATE_SERVICE_TIME, 
            TecRegMockup.getObservable(TecRegMockup.PROP_SERVICE_TIME, STAGE_MIGRATE) );
         */

        manualObservables = new HashMap<String, List<MeasurementImpl>>();
        manualObservables.put(STAGE_MIGRATE, new Vector<MeasurementImpl>());
    }

    /* ------------------------------------------------------------- */

    /** Parameters for the workflow execution etc */
    HashMap<String, String> parameters = new HashMap<String, String>();
    /** The holder for the identifier service. */
    Migrate migrator = null;
    URL migratorEndpoint = null;

    /* ------------------------------------------------------------- */

    Characterise dpPre = null;
    Characterise dpPost = null;
    Identify idPre = null;
    Identify idPost = null;

    /* ------------------------------------------------------------- */

    private static final FormatRegistry format = FormatRegistryFactory.getFormatRegistry();

    /* (non-Javadoc)
     * @see eu.planets_project.tb.impl.services.mockups.workflow.ExperimentWorkflow#getStages()
     */
    public List<ExperimentStageBean> getStages() {
        List<ExperimentStageBean> stages = new Vector<ExperimentStageBean>();
        stages.add(new ExperimentStageBean(STAGE_PRE_MIGRATE, "Characterise before migration."));
        stages.add(new ExperimentStageBean(STAGE_MIGRATE, "Migrate the digital object."));
        stages.add(new ExperimentStageBean(STAGE_POST_MIGRATE, "Characterise after migration."));
        return stages;
    }

    /* (non-Javadoc)
     * @see eu.planets_project.tb.impl.services.mockups.workflow.ExperimentWorkflow#getManualObservables()
     */
    public HashMap<String, List<MeasurementImpl>> getManualObservables() {
        return manualObservables;
    }

    /* (non-Javadoc)
     * @see eu.planets_project.tb.impl.services.mockups.workflow.ExperimentWorkflow#getObservables()
     */
    public HashMap<String, List<MeasurementImpl>> getObservables() {
        // Copy and augment with properties for the format type:
        HashMap<String, List<MeasurementImpl>> obs = new HashMap<String, List<MeasurementImpl>>();
        for (String stage : observables.keySet()) {
            obs.put(stage, new Vector<MeasurementImpl>());
            for (MeasurementImpl m : observables.get(stage)) {
                obs.get(stage).add(m);
            }
        }

        /* --------------------------------------------------------------------- */

        // Pre-characterise:
        if (obs.get(STAGE_PRE_MIGRATE) == null)
            obs.put(STAGE_PRE_MIGRATE, new Vector<MeasurementImpl>());
        // For Characterise:
        if (this.preIsCharacterise()) {
            for (MeasurementImpl m : this.getMeasurementsForInFormat(this.getFromFormat())) {
                obs.get(STAGE_PRE_MIGRATE).add(m);
            }
        }
        // For Identify:
        if (this.preIsIdentify()) {
            obs.get(STAGE_PRE_MIGRATE).add(IdentifyWorkflow.MEASURE_IDENTIFY_FORMAT);
            obs.get(STAGE_PRE_MIGRATE).add(IdentifyWorkflow.MEASURE_IDENTIFY_METHOD);
            obs.get(STAGE_PRE_MIGRATE).add(TecRegMockup.getObservable(TecRegMockup.PROP_DO_SIZE));
        }
        // In general:
        if (this.preIsDefined()) {
            // Add basic properties.
            obs.get(STAGE_PRE_MIGRATE)
                    .add(TecRegMockup.getObservable(TecRegMockup.PROP_SERVICE_EXECUTION_SUCEEDED));
            obs.get(STAGE_PRE_MIGRATE).add(TecRegMockup.getObservable(TecRegMockup.PROP_SERVICE_TIME));
        }

        /* --------------------------------------------------------------------- */

        // Post-characterise:
        if (obs.get(STAGE_POST_MIGRATE) == null)
            obs.put(STAGE_POST_MIGRATE, new Vector<MeasurementImpl>());
        // For Characterise:
        if (this.postIsCharacterise()) {
            for (MeasurementImpl m : this.getMeasurementsForOutFormat(this.getToFormat())) {
                obs.get(STAGE_POST_MIGRATE).add(m);
            }
        }
        // For Identify:
        if (this.postIsIdentify()) {
            obs.get(STAGE_POST_MIGRATE).add(IdentifyWorkflow.MEASURE_IDENTIFY_FORMAT);
            obs.get(STAGE_POST_MIGRATE).add(IdentifyWorkflow.MEASURE_IDENTIFY_METHOD);
            obs.get(STAGE_POST_MIGRATE).add(TecRegMockup.getObservable(TecRegMockup.PROP_DO_SIZE));
        }
        // In general:
        if (this.postIsDefined()) {
            // Add basic properties.
            obs.get(STAGE_POST_MIGRATE)
                    .add(TecRegMockup.getObservable(TecRegMockup.PROP_SERVICE_EXECUTION_SUCEEDED));
            obs.get(STAGE_POST_MIGRATE).add(TecRegMockup.getObservable(TecRegMockup.PROP_SERVICE_TIME));
        }

        return obs;
    }

    private String cacheInFormat = "";
    private List<MeasurementImpl> cacheInProps = null;
    private String cacheOutFormat = "";
    private List<MeasurementImpl> cacheOutProps = null;

    private List<MeasurementImpl> getMeasurementsForInFormat(String format) {
        if (format == null)
            return new Vector<MeasurementImpl>();
        if (!format.equals(cacheInFormat) || cacheInProps == null) {
            cacheInProps = this.getMeasurementsForFormat(format, dpPre);
            cacheInFormat = format;
        }
        return cacheInProps;
    }

    private List<MeasurementImpl> getMeasurementsForOutFormat(String format) {
        if (format == null)
            return new Vector<MeasurementImpl>();
        if (!format.equals(cacheOutFormat) || cacheOutProps == null) {
            cacheOutProps = this.getMeasurementsForFormat(format, dpPost);
            cacheOutFormat = format;
        }
        return cacheOutProps;
    }

    /**
     * Creates a list of MeasurementImpl for the requested format and Characterise service.
     * Properties are requested from the service's .listProperties(puid) method. 
     * @param format
     * @param dp
     * @return
     */
    private List<MeasurementImpl> getMeasurementsForFormat(String format, Characterise dp) {
        List<MeasurementImpl> lm = new Vector<MeasurementImpl>();

        HashMap<URI, MeasurementImpl> meas = new HashMap<URI, MeasurementImpl>();
        URI formatURI;
        if (format == null) {
            log.error("Format was set to NULL.");
            return lm;
        }
        try {
            formatURI = new URI(format);
        } catch (URISyntaxException e) {
            e.printStackTrace();
            return lm;
        }
        if (dp == null) {
            return lm;
        }
        // Find all the PRONOM IDs for this format URI:
        for (URI puid : this.getPronomURIAliases(formatURI)) {
            List<Property> measurableProperties = dp.listProperties(puid);
            if (measurableProperties != null) {
                for (Property p : measurableProperties) {
                    MeasurementImpl m = this.createMeasurementFromProperty(p);
                    if (!meas.containsKey(m.getIdentifier())) {
                        meas.put(m.getIdentifierUri(), m);
                    }
                }
            }
        }

        lm = new Vector<MeasurementImpl>(meas.values());
        //Collections.sort( lm );
        return lm;
    }

    /**
     * Takes a Property that's used in Planets level-one service call results
     * and converts it into the Testbed's Property model element: MeasurementImpl
     * @param p eu.planets_project.services.datatypes.Property
     * @return
     */
    private MeasurementImpl createMeasurementFromProperty(Property p) {
        MeasurementImpl m = new MeasurementImpl();

        if (p == null)
            return m;

        URI propURI = p.getUri();
        // Invent a uri if required:
        if (propURI == null) {
            try {
                propURI = new URI(TecRegMockup.URI_XCDL_PROP_ROOT + p.getName());
            } catch (URISyntaxException e) {
                e.printStackTrace();
                return m;
            }
            // Rebuild the property:
            p = new Property.Builder(propURI).name(p.getName()).description(p.getDescription()).type(p.getType())
                    .unit(p.getUnit()).value(p.getValue()).build();
        }

        // Copy in:
        m.setProperty(p);
        m.getTarget().setType(MeasurementTarget.TargetType.DIGITAL_OBJECT);
        m.setValue(p.getValue());

        return m;
    }

    private List<URI> getPronomURIAliases(URI typeURI) {
        Set<URI> turis = new HashSet<URI>();

        Format fmt = format.getFormatForUri(typeURI);
        if (format.isUriOfType(typeURI, UriType.MIME)) {
            Set<URI> furis = ServiceBrowser.fr.getUrisForMimeType(fmt.getMimeTypes().iterator().next());
            turis.addAll(furis);
        } else if (format.isUriOfType(typeURI, UriType.EXTENSION)) {
            Set<URI> furis = ServiceBrowser.fr.getUrisForExtension(fmt.getExtensions().iterator().next());
            turis.addAll(furis);
        } else {
            // Aliases:
            for (URI uri : fmt.getAliases()) {
                turis.add(uri);
            }
        }
        return new ArrayList<URI>(turis);
    }

    /* (non-Javadoc)
     * @see eu.planets_project.tb.impl.services.mockups.workflow.ExperimentWorkflow#setParameters(java.util.HashMap)
     */
    public void setParameters(HashMap<String, String> parameters) throws Exception {
        this.parameters = parameters;
        // Attempt to connect to the Migrate service.
        try {
            migratorEndpoint = new URL(this.parameters.get(PARAM_SERVICE));
            migrator = new MigrateWrapper(migratorEndpoint);
        } catch (MalformedURLException e) {
            throw new Exception("You did not specify a valid migration service URL!");
        }

        // Also set the pre services:
        try {
            if (this.preIsCharacterise()) {
                dpPre = new CharacteriseWrapper(new URL(this.parameters.get(PARAM_PRE_SERVICE)));
            } else {
                dpPre = null;
            }
            if (this.preIsIdentify()) {
                idPre = new IdentifyWrapper(new URL(this.parameters.get(PARAM_PRE_SERVICE)));

            } else {
                idPre = null;
            }
        } catch (MalformedURLException e) {
            throw new Exception("You did not specify a valid pre-migration service URL!");
        }

        // Also set the post services:
        try {
            if (this.postIsCharacterise()) {
                dpPost = new CharacteriseWrapper(new URL(this.parameters.get(PARAM_POST_SERVICE)));
            } else {
                dpPost = null;
            }
            if (this.postIsIdentify()) {
                idPost = new IdentifyWrapper(new URL(this.parameters.get(PARAM_POST_SERVICE)));

            } else {
                idPost = null;
            }
        } catch (MalformedURLException e) {
            throw new Exception("You did not specify a valid post-migration service URL!");
        }

        // FIXME Also create/record a ServiceRecordImpl? 

        // MUST throw an Exception if the input and outputs are not defined!
        if (this.getFromFormat() == null || "".equals(this.getFromFormat()) || this.getToFormat() == null
                || "".equals(this.getToFormat())) {
            throw new Exception("You must specify both the input and output format!");
        }
    }

    public HashMap<String, String> getParameters() {
        return this.parameters;
    }

    private boolean preIsCharacterise() {
        if (!this.preIsDefined())
            return false;
        return this.parameters.get(PARAM_PRE_SERVICE_TYPE).equals(SERVICE_TYPE_CHARACTERISE);
    }

    private boolean postIsCharacterise() {
        if (!this.postIsDefined())
            return false;
        return this.parameters.get(PARAM_POST_SERVICE_TYPE).equals(SERVICE_TYPE_CHARACTERISE);
    }

    private boolean preIsIdentify() {
        if (!this.preIsDefined())
            return false;
        return this.parameters.get(PARAM_PRE_SERVICE_TYPE).equals(SERVICE_TYPE_IDENTIFY);
    }

    private boolean postIsIdentify() {
        if (!this.postIsDefined())
            return false;
        return this.parameters.get(PARAM_POST_SERVICE_TYPE).equals(SERVICE_TYPE_IDENTIFY);
    }

    private boolean preIsDefined() {
        if (this.parameters.get(PARAM_PRE_SERVICE_TYPE) == null
                || "".equals(this.parameters.get(PARAM_PRE_SERVICE_TYPE)))
            return false;
        return true;
    }

    private boolean postIsDefined() {
        if (this.parameters.get(PARAM_POST_SERVICE_TYPE) == null
                || "".equals(this.parameters.get(PARAM_POST_SERVICE_TYPE)))
            return false;
        return true;
    }

    private String getFromFormat() {
        log.info("getFromFormat: " + this.parameters.get(PARAM_FROM));
        return this.parameters.get(PARAM_FROM);
    }

    private String getToFormat() {
        log.info("getToFormat: " + this.parameters.get(PARAM_TO));
        return this.parameters.get(PARAM_TO);
    }

    /* ---------------------------------------------------------------------------------------- */
    /* ---------------------------------------------------------------------------------------- */
    /* ---------------------------------------------------------------------------------------- */

    /* (non-Javadoc)
     * @see eu.planets_project.tb.impl.services.mockups.workflow.ExperimentWorkflow#execute(eu.planets_project.services.datatypes.DigitalObject, java.util.HashMap)
     */
    public WorkflowResult execute(DigitalObject dob) {
        // Initialise the result:
        WorkflowResult wr = new WorkflowResult();

        // Pre-migrate characterise
        ExecutionStageRecordImpl preStage = new ExecutionStageRecordImpl(null, STAGE_PRE_MIGRATE);
        try {
            wr.getStages().add(preStage);
            if (this.preIsCharacterise()) {
                executeCharacteriseStage(wr, dob, preStage, dpPre);
            }
            if (this.preIsIdentify()) {
                executeIdentifyStage(wr, dob, preStage, idPre);
            }
        } catch (Exception e) {
            log.error("Pre-migrate stage failed! " + e);
            e.printStackTrace();
        }

        // Migrate Stage:
        ExecutionStageRecordImpl migrateStage = new ExecutionStageRecordImpl(null, STAGE_MIGRATE);
        try {
            wr.getStages().add(migrateStage);
            executeMigrateStage(wr, migrateStage, dob);
        } catch (Exception e) {
            // Create a ServiceReport from the exception.
            // URGENT can we distinguish tool and install error here?
            ServiceReport sr = new ServiceReport(Type.ERROR, Status.TOOL_ERROR, e.toString());
            wr.logReport(sr);
            log.error("Migration failed! " + e);
            e.printStackTrace();
            return wr;
        }

        // Post-migrate characterise
        ExecutionStageRecordImpl postStage = new ExecutionStageRecordImpl(null, STAGE_POST_MIGRATE);
        try {
            wr.getStages().add(postStage);
            if (this.postIsCharacterise()) {
                executeCharacteriseStage(wr, (DigitalObject) wr.getResult(), postStage, dpPost);
            }
            if (this.postIsIdentify()) {
                executeIdentifyStage(wr, (DigitalObject) wr.getResult(), postStage, idPost);
            }
        } catch (Exception e) {
            log.error("Post-Migrate stage failed! " + e);
            e.printStackTrace();
        }

        return wr;
    }

    /**
     * The actual Migration stage.
     * 
     * @param wr
     * @param migrateStage 
     * @param dob
     * @throws Exception
     */
    private void executeMigrateStage(WorkflowResult wr, ExecutionStageRecordImpl migrateStage, DigitalObject dob)
            throws Exception {
        // Now prepare the result:
        List<MeasurementImpl> stage_m = migrateStage.getMeasurements();

        // Record the endpoint of the service used for this stage.
        migrateStage.setEndpoint(migratorEndpoint);

        // Invoke the service, timing it along the way:
        boolean success = true;
        MigrateResult migrated = null;
        long msBefore = 0, msAfter = 0;
        URI from = null, to = null;
        msBefore = System.currentTimeMillis();
        try {
            log.info("Migrating " + dob);
            from = new URI(getFromFormat());
            to = new URI(getToFormat());
            migrated = migrator.migrate(dob, from, to, null);
        } catch (Exception e) {
            success = false;
            e.printStackTrace();
            throw new Exception("Service Invocation Failed! : " + e.getMessage());
        }
        msAfter = System.currentTimeMillis();

        // Compute the run time.
        stage_m.add(new MeasurementImpl(TecRegMockup.PROP_SERVICE_TIME, "" + ((msAfter - msBefore) / 1000.0)));
        // Add the object size:
        stage_m.add(new MeasurementImpl(TecRegMockup.PROP_DO_SIZE, "" + IdentifyWorkflow.getContentSize(dob)));

        // Now record
        if (success && migrated.getDigitalObject() != null) {

            stage_m.add(new MeasurementImpl(TecRegMockup.PROP_SERVICE_EXECUTION_SUCEEDED, "true"));

            // Take the digital object, put it in a temp file, and give it a sensible name, using the new format extension.
            File doTmp = File.createTempFile("migrateResult", ".tmp");
            doTmp.deleteOnExit();
            DigitalObjectUtils.toFile(migrated.getDigitalObject(), doTmp);
            DigitalObject.Builder newdob = new DigitalObject.Builder(migrated.getDigitalObject());
            newdob.content(Content.byReference(doTmp));
            // FIXME The above need to be a full recursive storage operation!

            if (to != null) {
                //Format f = new Format(to);
                Set<String> extensionsTo = ServiceBrowser.fr.getExtensions(to);
                String title = dob.getTitle();
                if (extensionsTo.iterator().hasNext()) {
                    title += "." + extensionsTo.iterator().next();
                }
                title = title.substring(title.lastIndexOf("/") + 1);
                newdob.title(title);
            }
            wr.setResult(newdob.build());
            wr.setResultType(WorkflowResult.RESULT_DIGITAL_OBJECT);
            wr.logReport(migrated.getReport());
            log.info("Migration succeeded.");
            return;
        }

        // Only get to here if there was not a valid result.

        // Build in a 'service failed' property, i.e. the call worked, but no result.
        stage_m.add(new MeasurementImpl(TecRegMockup.PROP_SERVICE_EXECUTION_SUCEEDED, "false"));

        // ADD a report, so the full set is known.
        wr.logReport(migrated.getReport());

        // FIXME Should now throw an Exception, as the WF cannot proceed?
        throw new Exception(
                "Migration failed.  No Digital Object was created. " + migrated.getReport().getMessage());

        // FIXME Add a 'toString' on the report that makes a text summary of the whole state?

    }

    /**
     * 
     * @param wr
     * @param dob
     * @param stage
     * @throws Exception
     */
    private void executeCharacteriseStage(WorkflowResult wr, DigitalObject dob, ExecutionStageRecordImpl stage,
            Characterise dp) throws Exception {
        // Now prepare the result:
        List<MeasurementImpl> stage_m = stage.getMeasurements();

        // Invoke the service, timing it along the way:
        boolean success = true;
        CharacteriseResult result = null;
        long msBefore = 0, msAfter = 0;
        msBefore = System.currentTimeMillis();
        try {
            log.info("Characterising " + dob);
            //FIXME this is a hack for disabling norm data for XCDL characterisation services 
            // as parameters are currently not definable for this expType
            List<Parameter> parameterList = new ArrayList<Parameter>();
            parameterList.add(new Parameter("disableNormDataInXCDL", "-n"));
            result = dp.characterise(dob, parameterList);
            wr.logReport(result.getReport());
        } catch (Exception e) {
            log.error("Characterisation failed with exception: " + e);
            e.printStackTrace();
            success = false;
        }
        msAfter = System.currentTimeMillis();
        if (success) {
            log.info("Characterisation succeeded: " + result);
            if (result != null) {
                // URGENT Formalise and refactor this logic.
                log.info("Service Report: " + result.getReport().getMessage());
                log.info("Service Report: " + result.getReport().getStatus());
                log.info("Service Report: " + result.getReport().getType());
                if (result.getReport().getStatus() == Status.INSTALLATION_ERROR
                        || result.getReport().getStatus() == Status.TOOL_ERROR) {
                    success = false;
                }
            }
        }

        // Compute the run time.
        stage_m.add(new MeasurementImpl(TecRegMockup.PROP_SERVICE_TIME, "" + ((msAfter - msBefore) / 1000.0)));
        // Add the object size:
        stage_m.add(new MeasurementImpl(TecRegMockup.PROP_DO_SIZE, "" + IdentifyWorkflow.getContentSize(dob)));

        // Record results:
        if (success) {
            stage_m.add(new MeasurementImpl(TecRegMockup.PROP_SERVICE_EXECUTION_SUCEEDED, "true"));
            if (result != null && result.getProperties() != null) {
                log.info("Got " + result.getProperties().size() + " properties");
                for (Property p : result.getProperties()) {
                    log.info("Recording measurement: " + p.getUri() + ":" + p.getName() + " = " + p.getValue());
                    stage_m.add(new MeasurementImpl(p.getUri(), p.getValue()));
                }
            }
            return;
        }

        // FAILED:
        stage_m.add(new MeasurementImpl(TecRegMockup.PROP_SERVICE_EXECUTION_SUCEEDED, "false"));

    }

    /**
     * FIXME Code duplication between this and the actual IdentifyWorkflow.  Can we clean up more?
     * 
     * @param wr
     * @param result
     * @param stagePostMigrate
     * @param idPost2
     */
    private void executeIdentifyStage(WorkflowResult wr, DigitalObject dob, ExecutionStageRecordImpl stage,
            Identify identify) throws Exception {
        // Now prepare the result:
        List<MeasurementImpl> stage_m = stage.getMeasurements();

        // Invoke the service, timing it along the way:
        boolean success = true;
        IdentifyResult result = null;
        long msBefore = 0, msAfter = 0;
        msBefore = System.currentTimeMillis();
        try {
            log.info("Identifying " + dob);
            result = identify.identify(dob, null);
            wr.logReport(result.getReport());
        } catch (Exception e) {
            log.error("Identification failed with exception: " + e);
            e.printStackTrace();
            success = false;
        }
        msAfter = System.currentTimeMillis();
        if (success) {
            log.info("Identification succeeded: " + result);
            if (result != null) {
                // URGENT Formalise and refactor this logic.
                log.info("Service Report: " + result.getReport().getMessage());
                log.info("Service Report: " + result.getReport().getStatus());
                log.info("Service Report: " + result.getReport().getType());
                if (result.getReport().getStatus() == Status.INSTALLATION_ERROR
                        || result.getReport().getStatus() == Status.TOOL_ERROR) {
                    success = false;
                }
            }
        }

        // Compute the run time.
        stage_m.add(new MeasurementImpl(TecRegMockup.PROP_SERVICE_TIME, "" + ((msAfter - msBefore) / 1000.0)));
        // Add the object size:
        stage_m.add(new MeasurementImpl(TecRegMockup.PROP_DO_SIZE, "" + IdentifyWorkflow.getContentSize(dob)));

        // Record results:
        if (success) {
            try {
                stage_m.add(new MeasurementImpl(TecRegMockup.PROP_SERVICE_EXECUTION_SUCEEDED, "true"));
                log.info("Start with Measurements #" + stage_m.size());
                IdentifyWorkflow.collectIdentifyResults(stage_m, result, dob);
                log.info("Afterwards, Measurements #" + stage_m.size());
                return;
            } catch (Exception e) {
                log.error("Failed to record identification results: " + e);
            }
        }

        // FAILED:
        stage_m.add(new MeasurementImpl(TecRegMockup.PROP_SERVICE_EXECUTION_SUCEEDED, "false"));

    }

}