org.apache.taverna.commandline.data.InputsHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.taverna.commandline.data.InputsHandler.java

Source

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.apache.taverna.commandline.data;

import static org.apache.taverna.commandline.CommandLineUtils.safeIsSameFile;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.apache.taverna.commandline.exceptions.InputMismatchException;
import org.apache.taverna.commandline.exceptions.InvalidOptionException;
import org.apache.taverna.commandline.exceptions.ReadInputException;
import org.apache.taverna.commandline.options.CommandLineOptions;
import org.apache.taverna.databundle.DataBundles;
import org.apache.taverna.robundle.Bundle;
import org.apache.taverna.scufl2.api.port.InputWorkflowPort;

/**
 * Handles the reading, or processing, or input values according to arguments provided to the
 * commandline.
 * The may be either as direct values, from a file, or from a Baclava document.
 * Also handles registering the input values with the Data Service, ready to initiate
 * the workflow run.
 *
 * @author Stuart Owen
 */
public class InputsHandler {

    private static Logger logger = Logger.getLogger(InputsHandler.class);

    public void checkProvidedInputs(Map<String, InputWorkflowPort> portMap, CommandLineOptions options)
            throws InputMismatchException, IOException {
        if (options.hasInputBundle()) {
            try (Bundle inputBundle = openInputBundle(options)) {
                Path inputs = DataBundles.getInputs(inputBundle);
                Set<String> expected = portMap.keySet();
                Set<String> provided = DataBundles.getPorts(inputs).keySet();
                if (!provided.containsAll(expected)) {
                    throw new InputMismatchException("Missing inputs", expected, provided);
                }
            }
            // inputFiles/values ignored if input bundle is provided
            return;
        }

        // we dont check for the document
        Set<String> providedInputNames = new HashSet<String>();
        for (int i = 0; i < options.getInputFiles().length; i += 2) {
            // If it already contains a value for the input port, e.g
            // two inputs are provided for the same port
            if (providedInputNames.contains(options.getInputFiles()[i])) {
                throw new InputMismatchException("Two input values were provided for the same input port "
                        + options.getInputFiles()[i] + ".", null, null);
            }
            providedInputNames.add(options.getInputFiles()[i]);
        }

        for (int i = 0; i < options.getInputValues().length; i += 2) {
            // If it already contains a value for the input port, e.g
            // two inputs are provided for the same port
            if (providedInputNames.contains(options.getInputValues()[i])) {
                throw new InputMismatchException("Two input values were provided for the same input port "
                        + options.getInputValues()[i] + ".", null, null);
            }
            providedInputNames.add(options.getInputValues()[i]);
        }

        if (portMap.size() * 2 != (options.getInputFiles().length + options.getInputValues().length)) {
            throw new InputMismatchException(
                    "The number of inputs provided does not match the number of input ports.", portMap.keySet(),
                    providedInputNames);
        }

        for (String portName : portMap.keySet()) {
            if (!providedInputNames.contains(portName)) {
                throw new InputMismatchException(
                        "The provided inputs does not contain an input for the port '" + portName + "'",
                        portMap.keySet(), providedInputNames);
            }
        }
    }

    public Bundle registerInputs(Map<String, InputWorkflowPort> portMap, CommandLineOptions options)
            throws InvalidOptionException, ReadInputException, IOException {

        Bundle inputDataBundle = openInputBundle(options);
        if (Boolean.getBoolean("debug.bundle")) {
            inputDataBundle.setDeleteOnClose(false);
            System.out.println("Bundle: " + inputDataBundle.getSource());
        }

        Path inputs = DataBundles.getInputs(inputDataBundle);

        URL url;
        try {
            url = new URL("file:");
        } catch (MalformedURLException e1) {
            // Should never happen, but just in case:
            throw new ReadInputException(
                    "The was an internal error setting up the URL to open the inputs. You should contact Taverna support.",
                    e1);
        }

        if (options.hasInputFiles()) {
            regesterInputsFromFiles(portMap, options, inputs, url);
        }

        if (options.hasInputValues()) {
            registerInputsFromValues(portMap, options, inputs);

        }

        return inputDataBundle;
    }

    private Bundle openInputBundle(CommandLineOptions options) throws IOException {
        if (!options.hasInputBundle()) {
            return DataBundles.createBundle();
        }
        Path inputBundlePath = Paths.get(options.getInputBundle());
        if (!Files.isReadable(inputBundlePath)) {
            throw new IOException("Can't read inputbundle: " + inputBundlePath);
        }
        if (options.hasSaveResultsToBundle()
                && safeIsSameFile(inputBundlePath, Paths.get(options.getSaveResultsToBundle()))) {
            // Note: It is valid to do -bundle same.zip -inputbundle same.zip, 
            // in which case we should NOT open it as readOnly, as that
            // might make a copy unnecessarily. In case of symlinks we'll use the
            // path from -bundle to avoid double-opening later
            return DataBundles.openBundle(Paths.get(options.getSaveResultsToBundle()));
        } else {
            return DataBundles.openBundleReadOnly(inputBundlePath);
        }
    }

    private void registerInputsFromValues(Map<String, InputWorkflowPort> portMap, CommandLineOptions options,
            Path inputs) throws InvalidOptionException {
        String[] inputParams = options.getInputValues();
        for (int i = 0; i < inputParams.length; i = i + 2) {
            String inputName = inputParams[i];
            try {
                String inputValue = inputParams[i + 1];
                InputWorkflowPort port = portMap.get(inputName);

                if (port == null) {
                    throw new InvalidOptionException("Cannot find an input port named '" + inputName + "'");
                }

                Path portPath = DataBundles.getPort(inputs, inputName);
                if (options.hasDelimiterFor(inputName)) {
                    String delimiter = options.inputDelimiter(inputName);
                    Object value = checkForDepthMismatch(1, port.getDepth(), inputName,
                            inputValue.split(delimiter));
                    setValue(portPath, value);
                } else {
                    Object value = checkForDepthMismatch(0, port.getDepth(), inputName, inputValue);
                    setValue(portPath, value);
                }

            } catch (IndexOutOfBoundsException e) {
                throw new InvalidOptionException("Missing input value for input " + inputName);
            } catch (IOException e) {
                throw new InvalidOptionException("Error creating value for input " + inputName);
            }
        }
    }

    private void regesterInputsFromFiles(Map<String, InputWorkflowPort> portMap, CommandLineOptions options,
            Path inputs, URL url) throws InvalidOptionException {
        String[] inputParams = options.getInputFiles();
        for (int i = 0; i < inputParams.length; i = i + 2) {
            String inputName = inputParams[i];
            try {
                URL inputURL = new URL(url, inputParams[i + 1]);
                InputWorkflowPort port = portMap.get(inputName);

                if (port == null) {
                    throw new InvalidOptionException("Cannot find an input port named '" + inputName + "'");
                }

                Path portPath = DataBundles.getPort(inputs, inputName);
                if (options.hasDelimiterFor(inputName)) {
                    String delimiter = options.inputDelimiter(inputName);
                    Object value = IOUtils.toString(inputURL.openStream()).split(delimiter);
                    value = checkForDepthMismatch(1, port.getDepth(), inputName, value);
                    setValue(portPath, value);
                } else {
                    Object value = IOUtils.toByteArray(inputURL.openStream());
                    value = checkForDepthMismatch(0, port.getDepth(), inputName, value);
                    setValue(portPath, value);
                }
            } catch (IndexOutOfBoundsException e) {
                throw new InvalidOptionException("Missing input filename for input " + inputName);
            } catch (IOException e) {
                throw new InvalidOptionException("Could not read input " + inputName + ": " + e.getMessage());
            }
        }
    }

    private void setValue(Path port, Object userInput) throws IOException {
        if (userInput instanceof File) {
            DataBundles.setReference(port, ((File) userInput).toURI());
        } else if (userInput instanceof URL) {
            try {
                DataBundles.setReference(port, ((URL) userInput).toURI());
            } catch (URISyntaxException e) {
                logger.warn(String.format("Error converting %1$s to URI", userInput), e);
            }
        } else if (userInput instanceof String) {
            DataBundles.setStringValue(port, (String) userInput);
        } else if (userInput instanceof byte[]) {
            Files.write(port, (byte[]) userInput, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
        } else if (userInput instanceof List<?>) {
            DataBundles.createList(port);
            List<?> list = (List<?>) userInput;
            for (Object object : list) {
                setValue(DataBundles.newListItem(port), object);
            }
        } else {
            logger.warn("Unknown input type : " + userInput.getClass().getName());
        }
    }

    private Object checkForDepthMismatch(int inputDepth, int portDepth, String inputName, Object inputValue)
            throws InvalidOptionException {
        if (inputDepth != portDepth) {
            if (inputDepth < portDepth) {
                logger.warn("Wrapping input for '" + inputName + "' from a depth of " + inputDepth
                        + " to the required depth of " + portDepth);
                while (inputDepth < portDepth) {
                    List<Object> l = new ArrayList<Object>();
                    l.add(inputValue);
                    inputValue = l;
                    inputDepth++;
                }
            } else {
                String msg = "There is a mismatch between depth of the list for the input port '" + inputName
                        + "' and the data presented. The input port requires a " + depthToString(portDepth)
                        + " and the data presented is a " + depthToString(inputDepth);
                throw new InvalidOptionException(msg);
            }
        }

        return inputValue;
    }

    private String depthToString(int depth) {
        switch (depth) {
        case 0:
            return "single item";
        case 1:
            return "list";
        case 2:
            return "list of lists";
        default:
            return "list of depth " + depth;
        }
    }

    private int getObjectDepth(Object o) {
        int result = 0;
        if (o instanceof Iterable) {
            result++;
            Iterator i = ((Iterable) o).iterator();

            if (i.hasNext()) {
                Object child = i.next();
                result = result + getObjectDepth(child);
            }
        }
        return result;
    }
}