class GenericMigrationWrapper {

    @SuppressWarnings("boxing")
	public MigrateResult migrate(DigitalObject digitalObject, URI inputFormat,
	    URI outputFormat, List<Parameter> toolParameters)
	    throws MigrationException, ConfigurationException {

	final ServicePerformanceHelper servicePerformanceHelper = new ServicePerformanceHelper();
	final Date migrationStartTime = new Date();

	/*
	 * Validate that the proper parameters are set for the migration path
	 * identified by inputFormat and outputFormat
	 */
	final MigrationPath migrationPath = this.migrationPaths.getMigrationPath(
		inputFormat, outputFormat);

	// If called with null parameters, use an empty list instead
	if (toolParameters == null) {
	    this.log.warning("Called with null parameters. Assuming the caller ment"
		    + " to call with an empty list.");
	    toolParameters = new ArrayList<Parameter>();
	}

	// Prepare any necessary temporary files.
	final Map<String, File> temporaryFileMappings = createTemporaryFiles(migrationPath);

	// Prepare the data to migrate
	InputStream standardInputStream = null;
	final ToolIOProfile inputIOProfile = migrationPath
		.getToolInputProfile();
	if (inputIOProfile.usePipedIO()) {

	    // Serve the digital object through standard input
	    standardInputStream = digitalObject.getContent().getInputStream();
	} else {

	    // Serve the digital object through a temporary input file.
	    File inputTempFile = temporaryFileMappings.get(inputIOProfile
		    .getCommandLineFileLabel());
	    DigitalObjectUtils.toFile(digitalObject, inputTempFile);
	}

	// Create an executable command line for the process runner.
	final PRCommandBuilder commandBuilder = new PRCommandBuilder(
		this.environmentParameters);
	final List<String> prCommand = commandBuilder.buildCommand(
		migrationPath, toolParameters, temporaryFileMappings);

	if (this.log.isLoggable(Level.INFO)) {
	    String fullCommandLine = "";
	    for (String cmdfrag : prCommand) {
		fullCommandLine += cmdfrag + " ";
	    }
	    this.log.info("Executing command line: " + fullCommandLine);
	}

	// Execute the tool
	final ProcessRunner toolProcessRunner = new ProcessRunner();
	final boolean executionSuccessful = executeToolProcess(
		toolProcessRunner, prCommand, standardInputStream);

	// Delete temporary files. However, do NOT delete the output unless the
	// execution failed.

	final ToolIOProfile outputIOProfile = migrationPath
		.getToolOutputProfile();

	if ((outputIOProfile.usePipedIO() == false) && executionSuccessful) {
	    // OK, there should exist an output file. Avoid deleting it.
	    final String outputFileLabel = outputIOProfile
		    .getCommandLineFileLabel();
	    for (String tempFileLabel : temporaryFileMappings.keySet()) {
		if (outputFileLabel.equals(tempFileLabel) == false) {
		    temporaryFileMappings.get(tempFileLabel).delete();
		}
	    }
	} else {
	    // The output has been returned through a pipe, so it is safe to
	    // delete all files.
	    for (File tempFile : temporaryFileMappings.values()) {
		tempFile.delete();
	    }
	}

	if (executionSuccessful == false) {
	    return buildMigrationResult(migrationPath, digitalObject, null,
		    toolProcessRunner);
	}

	// Now create a digital object from the tools output.
	DigitalObject.Builder builder;

	final ParameterReader parameterReader = new ParameterReader(
		toolParameters);
	final boolean returnDataByReference = parameterReader
		.getBooleanParameter("returnByReference", true);

	final ToolIOProfile toolOutputProfile = migrationPath
		.getToolOutputProfile();
	if (toolOutputProfile.usePipedIO() == false) {

	    // The tool has written the output to a temporary file. Create a
	    // digital object based on that.
	    final File outputFile = temporaryFileMappings.get(toolOutputProfile
		    .getCommandLineFileLabel());
	    if (returnDataByReference) {
		builder = new DigitalObject.Builder(Content
			.byReference(outputFile));
		// We cannot tell when the temporary file can be deleted, so let
		// it live.
	    } else {
		builder = new DigitalObject.Builder(Content.byValue(outputFile));

		// It is now safe to delete the temporary file.
		outputFile.delete();
	    }
	} else {

	    // The tool has written the output to standard output. Create a
	    // digital object based on that output.
	    if (returnDataByReference) {
		// Direct the standard output contents to a temporary file.
		builder = new DigitalObject.Builder(Content
			.byReference(toolProcessRunner.getProcessOutput()));
	    } else {
		// Return the standard output contents by value.
		builder = new DigitalObject.Builder(Content
			.byValue(toolProcessRunner.getProcessOutput()));
	    }
	}

	final double migrationDuration = new Date().getTime()
		- migrationStartTime.getTime();

	builder.format(outputFormat);
	final Agent agent = new Agent(this.toolIdentifier, this.serviceDescription
		.getName(), this.serviceDescription.getType());

	String eventSummary = "Migration carried out by executing the command line:";
	for (String commandLineFragment : prCommand) {
	    eventSummary += " " + commandLineFragment;
	}
	eventSummary += "\n\nThe migration service was called with these parameters:\n\n";
	for (Parameter serviceParameter : toolParameters) {
	    eventSummary += serviceParameter.getName() + " = "
		    + serviceParameter.getValue() + "\n";
	}

	servicePerformanceHelper.stop();

	// Add information about the migration event to the digital object.
	final DateFormat defaultDateFormat = DateFormat.getDateInstance();
	final Event event = new Event(eventSummary, defaultDateFormat
		.format(migrationStartTime), migrationDuration, agent,
		servicePerformanceHelper.getPerformanceProperties());
	builder.events(event);

	final DigitalObject resultObject = builder.build();

	return buildMigrationResult(migrationPath, digitalObject, resultObject,
		toolProcessRunner);
    }

    public  GenericMigrationWrapper(Document configuration,
	    Configuration environmentSettings, String toolIdentifier);

}