ro.cs.products.Executor.java Source code

Java tutorial

Introduction

Here is the source code for ro.cs.products.Executor.java

Source

/*
 * Copyright (C) 2016 Cosmin Cara
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 3 of the License, or (at your option)
 * any later version.
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 *  with this program; if not, see http://www.gnu.org/licenses/
 */
package ro.cs.products;

import org.apache.commons.cli.*;
import ro.cs.products.base.AbstractSearch;
import ro.cs.products.base.ProductDescriptor;
import ro.cs.products.base.SensorType;
import ro.cs.products.base.TileMap;
import ro.cs.products.landsat.LandsatProductDescriptor;
import ro.cs.products.landsat.LandsatProductDownloader;
import ro.cs.products.landsat.LandsatSearch;
import ro.cs.products.landsat.LandsatTilesMap;
import ro.cs.products.sentinel2.ProductStore;
import ro.cs.products.sentinel2.SentinelProductDescriptor;
import ro.cs.products.sentinel2.SentinelProductDownloader;
import ro.cs.products.sentinel2.SentinelTilesMap;
import ro.cs.products.sentinel2.amazon.AmazonSearch;
import ro.cs.products.sentinel2.scihub.SciHubSearch;
import ro.cs.products.sentinel2.workaround.FillAnglesMethod;
import ro.cs.products.sentinel2.workaround.ProductInspector;
import ro.cs.products.util.*;

import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Executor execution class.
 *
 * @author Cosmin Cara
 */
public class Executor {

    private static Options options;
    private static Properties props;
    private static String version;
    private static BatchProgressListener batchProgressListener;
    private static ProgressListener fileProgressListener;

    static {
        options = new Options();

        /* Target folder */
        Option outFolder = Option.builder(Constants.PARAM_OUT_FOLDER).longOpt("out").argName("output.folder")
                .desc("The folder in which the products will be downloaded").hasArg().required().build();
        /* Input folder for offline angles correction */
        Option inFolder = Option.builder(Constants.PARAM_INPUT_FOLDER).longOpt("input").argName("input.folder")
                .desc("The folder in which the products are to be inspected").hasArg().required().build();
        OptionGroup folderGroup = new OptionGroup();
        folderGroup.addOption(outFolder);
        folderGroup.addOption(inFolder);
        options.addOptionGroup(folderGroup);

        /* Area of interest */
        Option optionArea = Option.builder(Constants.PARAM_AREA).longOpt("area").argName("lon1,lat1 lon2,lat2 ...")
                .desc("A closed polygon whose vertices are given in <lon,lat> pairs, space-separated").hasArgs()
                .optionalArg(true).valueSeparator(' ').build();
        /* File containing the area of interest */
        Option optionAreaFile = Option.builder(Constants.PARAM_AREA_FILE).longOpt("areafile").argName("aoi.file")
                .desc("The file containing a closed polygon whose vertices are given in <lon lat> pairs, comma-separated")
                .hasArg().optionalArg(true).build();
        /* file containing the Sentinel-2 tile extents */
        Option optionTileShapeFile = Option.builder(Constants.PARAM_TILE_SHAPE_FILE).longOpt("shapetiles")
                .argName("tile.shapes.file").desc("The kml file containing Sentinel-2 tile extents").hasArg()
                .optionalArg(true).build();
        OptionGroup areaGroup = new OptionGroup();
        areaGroup.addOption(optionArea);
        areaGroup.addOption(optionAreaFile);
        areaGroup.addOption(optionTileShapeFile);
        options.addOptionGroup(areaGroup);

        /* List of S2 tiles */
        Option optionTileList = Option.builder(Constants.PARAM_TILE_LIST).longOpt("tiles")
                .argName("tileId1 tileId2 ...").desc("A list of S2 tile IDs, space-separated").hasArgs()
                .optionalArg(true).valueSeparator(' ').build();
        /* File containing the list of S2 tiles */
        Option optionTileFile = Option.builder(Constants.PARAM_TILE_LIST_FILE).longOpt("tilefile")
                .argName("tile.file").desc("A file containing a list of S2 tile IDs, one tile id per line").hasArg()
                .optionalArg(true).build();
        OptionGroup tileGroup = new OptionGroup();
        tileGroup.addOption(optionTileList);
        tileGroup.addOption(optionTileFile);
        options.addOptionGroup(tileGroup);

        /* Product names */
        Option optionProductList = Option.builder(Constants.PARAM_PRODUCT_LIST).longOpt("products")
                .argName("product1 product2 ...").desc("A list of S2/L8 product names, space-separated").hasArgs()
                .optionalArg(true).valueSeparator(' ').build();
        /* File containing the product names */
        Option optionProductFile = Option.builder(Constants.PARAM_PRODUCT_LIST_FILE).longOpt("productfile")
                .argName("product.file")
                .desc("A file containing a list of S2/L8 products, one product name per line").hasArg()
                .optionalArg(true).build();
        OptionGroup productGroup = new OptionGroup();
        productGroup.addOption(optionProductList);
        productGroup.addOption(optionProductFile);
        options.addOptionGroup(productGroup);
        /* S2 products UUIDs */
        options.addOption(
                Option.builder(Constants.PARAM_PRODUCT_UUID_LIST).longOpt("uuid").argName("uuid1 uui2 ...")
                        .desc("A list of S2 product unique identifiers, as retrieved from SciHub, space-separated")
                        .hasArgs().optionalArg(true).valueSeparator(' ').build());
        /* SciHub user */
        options.addOption(Option.builder(Constants.PARAM_USER).longOpt("user").argName("user")
                .desc("User account to connect to SCIHUB").hasArg(true).required(false).build());
        /* SciHub password */
        options.addOption(Option.builder(Constants.PARAM_PASSWORD).longOpt("password").argName("password")
                .desc("Password to connect to SCIHUB").hasArg(true).required(false).build());
        /* Sensor/product type */
        options.addOption(Option.builder(Constants.SENSOR).longOpt("sensor").argName("enum").desc("S2|L8")
                .hasArg(true).required(false).build());
        /* Cloud coverage percentage */
        options.addOption(Option.builder(Constants.PARAM_CLOUD_PERCENTAGE).longOpt("cloudpercentage")
                .argName("number between 0 and 100")
                .desc("The threshold for cloud coverage of the products. Above this threshold, the products will be ignored. Default is 100.")
                .hasArg().optionalArg(true).build());
        /* Start date */
        options.addOption(Option.builder(Constants.PARAM_START_DATE).longOpt("startdate").argName("yyyy-MM-dd")
                .desc("Look for products from a specific date (formatted as yyyy-MM-dd). Default is current date -7 days")
                .hasArg().optionalArg(true).build());
        /* End date */
        options.addOption(Option.builder(Constants.PARAM_END_DATE).longOpt("enddate").argName("yyyy-MM-dd").desc(
                "Look for products up to (and including) a specific date (formatted as yyyy-MM-dd). Default is current date")
                .hasArg().optionalArg(true).build());
        /* Max number of returned query results */
        options.addOption(Option.builder(Constants.PARAM_RESULTS_LIMIT).longOpt("limit")
                .argName("integer greater than 1").desc("The maximum number of products returned. Default is 10.")
                .hasArg().optionalArg(true).build());
        /* Download store */
        options.addOption(Option.builder(Constants.PARAM_DOWNLOAD_STORE).longOpt("store").argName("AWS|SCIHUB")
                .desc("Store of products being downloaded. Supported values are AWS or SCIHUB").hasArg(true)
                .optionalArg(true).build());
        /* Relative orbit number */
        options.addOption(Option.builder(Constants.PARAM_RELATIVE_ORBIT).longOpt("relative.orbit")
                .argName("integer").desc("Relative orbit number").hasArg(true).optionalArg(true).build());
        /* Missing angles compensation method */
        options.addOption(Option.builder(Constants.PARAM_FILL_ANGLES).longOpt("ma").argName("NONE|NAN|INTERPOLATE")
                .desc("Interpolate missing angles grids (if some are absent)").hasArg(true).optionalArg(true)
                .build());
        /*
         * Flag parameters
         */
        /* Compression of downloads */
        options.addOption(Option.builder(Constants.PARAM_FLAG_COMPRESS).longOpt("zip").argName("zip")
                .desc("Compresses the product into a zip archive").hasArg(false).optionalArg(true).build());
        /* Deletion of files after compression */
        options.addOption(Option.builder(Constants.PARAM_FLAG_DELETE).longOpt("delete").argName("delete")
                .desc("Delete the product files after compression").hasArg(false).optionalArg(true).build());
        /* Uncompressed files download from SciHub */
        options.addOption(Option.builder(Constants.PARAM_FLAG_UNPACKED).longOpt("unpacked").argName("unpacked")
                .desc("Download unpacked products (SciHub only)").hasArg(false).optionalArg(true).build());
        /* Searching AWS instead of SciHub */
        options.addOption(Option.builder(Constants.PARAM_FLAG_SEARCH_AWS).longOpt("aws").argName("aws")
                .desc("Perform search directly into AWS (slower but doesn't go through SciHub)").hasArg(false)
                .optionalArg(true).build());
        /* Verbose logging/output */
        options.addOption(Option.builder(Constants.PARAM_VERBOSE).longOpt("verbose").argName("verbose")
                .desc("Produces verbose output/logs").hasArg(false).optionalArg(true).build());
        /*
         * Proxy parameters
         */
        options.addOption(Option.builder(Constants.PARAM_PROXY_TYPE).longOpt("proxy.type").argName("http|socks")
                .desc("Proxy type (http or socks)").hasArg(true).optionalArg(true).build());
        options.addOption(Option.builder(Constants.PARAM_PROXY_HOST).longOpt("proxy.host").argName("proxy.host")
                .desc("Proxy host").hasArg(true).optionalArg(true).build());
        options.addOption(Option.builder(Constants.PARAM_PROXY_PORT).longOpt("proxy.port")
                .argName("integer greater than 0").desc("Proxy port").hasArg(true).optionalArg(true).build());
        options.addOption(Option.builder(Constants.PARAM_PROXY_USER).longOpt("proxy.user").argName("proxy.user")
                .desc("Proxy user").hasArg(true).optionalArg(true).build());
        options.addOption(Option.builder(Constants.PARAM_PROXY_PASSWORD).longOpt("proxy.password")
                .argName("proxy.password").desc("Proxy password").hasArg(true).optionalArg(true).build());
        props = new Properties();
        try {
            props.load(Executor.class.getResourceAsStream("download.properties"));
            version = props.getProperty("version");
        } catch (IOException ignored) {
        }
    }

    public static void main(String[] args) throws Exception {
        if (args.length == 0) {
            HelpFormatter formatter = new HelpFormatter();
            formatter.printHelp("ProductDownload-" + version, options);
            System.exit(0);
        }
        CommandLineParser parser = new DefaultParser();
        CommandLine commandLine = parser.parse(options, args);
        System.exit(execute(commandLine));
    }

    public static int execute(String[] args) throws Exception {
        CommandLineParser parser = new DefaultParser();
        CommandLine commandLine = parser.parse(options, args);
        return execute(commandLine);
    }

    private static int execute(CommandLine commandLine) throws Exception {
        int retCode = ReturnCode.OK;
        CommandLineParser parser = new DefaultParser();
        String logFile = props.getProperty("master.log.file");
        String folder;
        boolean debugMode = commandLine.hasOption(Constants.PARAM_VERBOSE);
        Logger.CustomLogger logger;
        SensorType sensorType = commandLine.hasOption(Constants.SENSOR)
                ? Enum.valueOf(SensorType.class, commandLine.getOptionValue(Constants.SENSOR))
                : SensorType.S2;
        if (commandLine.hasOption(Constants.PARAM_INPUT_FOLDER)) {
            folder = commandLine.getOptionValue(Constants.PARAM_INPUT_FOLDER);
            Utilities.ensureExists(Paths.get(folder));
            Logger.initialize(Paths.get(folder, logFile).toAbsolutePath().toString(), debugMode);
            logger = Logger.getRootLogger();
            if (commandLine.hasOption(Constants.PARAM_VERBOSE)) {
                printCommandLine(commandLine);
            }
            if (sensorType == SensorType.L8) {
                logger.warn("Argument --input will be ignored for Landsat8");
            } else {
                String rootFolder = commandLine.getOptionValue(Constants.PARAM_INPUT_FOLDER);
                FillAnglesMethod fillAnglesMethod = Enum.valueOf(FillAnglesMethod.class,
                        commandLine.hasOption(Constants.PARAM_FILL_ANGLES)
                                ? commandLine.getOptionValue(Constants.PARAM_FILL_ANGLES).toUpperCase()
                                : FillAnglesMethod.NONE.name());
                if (!FillAnglesMethod.NONE.equals(fillAnglesMethod)) {
                    try {
                        Set<String> products = null;
                        if (commandLine.hasOption(Constants.PARAM_PRODUCT_LIST)) {
                            products = new HashSet<>();
                            for (String product : commandLine.getOptionValues(Constants.PARAM_PRODUCT_LIST)) {
                                if (!product.endsWith(".SAFE")) {
                                    products.add(product + ".SAFE");
                                } else {
                                    products.add(product);
                                }
                            }
                        }
                        ProductInspector inspector = new ProductInspector(rootFolder, fillAnglesMethod, products);
                        inspector.traverse();
                    } catch (IOException e) {
                        logger.error(e.getMessage());
                        retCode = ReturnCode.DOWNLOAD_ERROR;
                    }
                }
            }
        } else {
            folder = commandLine.getOptionValue(Constants.PARAM_OUT_FOLDER);
            Utilities.ensureExists(Paths.get(folder));
            Logger.initialize(Paths.get(folder, logFile).toAbsolutePath().toString(), debugMode);
            logger = Logger.getRootLogger();
            printCommandLine(commandLine);

            String proxyType = commandLine.hasOption(Constants.PARAM_PROXY_TYPE)
                    ? commandLine.getOptionValue(Constants.PARAM_PROXY_TYPE)
                    : nullIfEmpty(props.getProperty("proxy.type", null));
            String proxyHost = commandLine.hasOption(Constants.PARAM_PROXY_HOST)
                    ? commandLine.getOptionValue(Constants.PARAM_PROXY_HOST)
                    : nullIfEmpty(props.getProperty("proxy.host", null));
            String proxyPort = commandLine.hasOption(Constants.PARAM_PROXY_PORT)
                    ? commandLine.getOptionValue(Constants.PARAM_PROXY_PORT)
                    : nullIfEmpty(props.getProperty("proxy.port", null));
            String proxyUser = commandLine.hasOption(Constants.PARAM_PROXY_USER)
                    ? commandLine.getOptionValue(Constants.PARAM_PROXY_USER)
                    : nullIfEmpty(props.getProperty("proxy.user", null));
            String proxyPwd = commandLine.hasOption(Constants.PARAM_PROXY_PASSWORD)
                    ? commandLine.getOptionValue(Constants.PARAM_PROXY_PASSWORD)
                    : nullIfEmpty(props.getProperty("proxy.pwd", null));
            NetUtils.setProxy(proxyType, proxyHost, proxyPort == null ? 0 : Integer.parseInt(proxyPort), proxyUser,
                    proxyPwd);

            List<ProductDescriptor> products = new ArrayList<>();
            Set<String> tiles = new HashSet<>();
            Polygon2D areaOfInterest = new Polygon2D();

            ProductStore source = Enum.valueOf(ProductStore.class,
                    commandLine.getOptionValue(Constants.PARAM_DOWNLOAD_STORE, ProductStore.SCIHUB.toString()));

            if (sensorType == SensorType.S2 && !commandLine.hasOption(Constants.PARAM_FLAG_SEARCH_AWS)
                    && !commandLine.hasOption(Constants.PARAM_USER)) {
                throw new MissingOptionException("Missing SciHub credentials");
            }

            String user = commandLine.getOptionValue(Constants.PARAM_USER);
            String pwd = commandLine.getOptionValue(Constants.PARAM_PASSWORD);
            if (user != null && pwd != null && !user.isEmpty() && !pwd.isEmpty()) {
                String authToken = "Basic " + new String(Base64.getEncoder().encode((user + ":" + pwd).getBytes()));
                NetUtils.setAuthToken(authToken);
            }

            ProductDownloader downloader = sensorType.equals(SensorType.S2)
                    ? new SentinelProductDownloader(source, commandLine.getOptionValue(Constants.PARAM_OUT_FOLDER),
                            props)
                    : new LandsatProductDownloader(commandLine.getOptionValue(Constants.PARAM_OUT_FOLDER), props);

            TileMap tileMap = sensorType == SensorType.S2 ? SentinelTilesMap.getInstance()
                    : LandsatTilesMap.getInstance();

            if (commandLine.hasOption(Constants.PARAM_AREA)) {
                String[] points = commandLine.getOptionValues(Constants.PARAM_AREA);
                for (String point : points) {
                    areaOfInterest.append(Double.parseDouble(point.substring(0, point.indexOf(","))),
                            Double.parseDouble(point.substring(point.indexOf(",") + 1)));
                }
            } else if (commandLine.hasOption(Constants.PARAM_AREA_FILE)) {
                areaOfInterest = Polygon2D.fromWKT(new String(
                        Files.readAllBytes(Paths.get(commandLine.getOptionValue(Constants.PARAM_AREA_FILE))),
                        StandardCharsets.UTF_8));
            } else if (commandLine.hasOption(Constants.PARAM_TILE_SHAPE_FILE)) {
                String tileShapeFile = commandLine.getOptionValue(Constants.PARAM_TILE_SHAPE_FILE);
                if (Files.exists(Paths.get(tileShapeFile))) {
                    logger.info(String.format("Reading %s tiles extents", sensorType));
                    tileMap.fromKmlFile(tileShapeFile);
                    logger.info(String.valueOf(tileMap.getCount() + " tiles found"));
                }
            } else {
                if (tileMap.getCount() == 0) {
                    logger.info(String.format("Loading %s tiles extents", sensorType));
                    tileMap.read(Executor.class.getResourceAsStream(sensorType + "tilemap.dat"));
                    logger.info(String.valueOf(tileMap.getCount() + " tile extents loaded"));
                }
            }

            if (commandLine.hasOption(Constants.PARAM_TILE_LIST)) {
                Collections.addAll(tiles, commandLine.getOptionValues(Constants.PARAM_TILE_LIST));
            } else if (commandLine.hasOption(Constants.PARAM_TILE_LIST_FILE)) {
                tiles.addAll(
                        Files.readAllLines(Paths.get(commandLine.getOptionValue(Constants.PARAM_TILE_LIST_FILE))));
            }

            if (commandLine.hasOption(Constants.PARAM_PRODUCT_LIST)) {
                String[] uuids = commandLine.getOptionValues(Constants.PARAM_PRODUCT_UUID_LIST);
                String[] productNames = commandLine.getOptionValues(Constants.PARAM_PRODUCT_LIST);
                if (sensorType == SensorType.S2
                        && (!commandLine.hasOption(Constants.PARAM_DOWNLOAD_STORE) || ProductStore.SCIHUB.toString()
                                .equals(commandLine.getOptionValue(Constants.PARAM_DOWNLOAD_STORE)))
                        && (uuids == null || uuids.length != productNames.length)) {
                    logger.error("For the list of product names a corresponding list of UUIDs has to be given!");
                    return -1;
                }
                for (int i = 0; i < productNames.length; i++) {
                    ProductDescriptor productDescriptor = sensorType == SensorType.S2
                            ? new SentinelProductDescriptor(productNames[i])
                            : new LandsatProductDescriptor(productNames[i]);
                    if (uuids != null) {
                        productDescriptor.setId(uuids[i]);
                    }
                    products.add(productDescriptor);
                }
            } else if (commandLine.hasOption(Constants.PARAM_PRODUCT_LIST_FILE)) {
                for (String line : Files
                        .readAllLines(Paths.get(commandLine.getOptionValue(Constants.PARAM_PRODUCT_LIST_FILE)))) {
                    products.add(sensorType == SensorType.S2 ? new SentinelProductDescriptor(line)
                            : new LandsatProductDescriptor(line));
                }
            }

            double clouds;
            if (commandLine.hasOption(Constants.PARAM_CLOUD_PERCENTAGE)) {
                clouds = Double.parseDouble(commandLine.getOptionValue(Constants.PARAM_CLOUD_PERCENTAGE));
            } else {
                clouds = Constants.DEFAULT_CLOUD_PERCENTAGE;
            }
            String sensingStart;
            if (commandLine.hasOption(Constants.PARAM_START_DATE)) {
                String dateString = commandLine.getOptionValue(Constants.PARAM_START_DATE);
                LocalDate startDate = LocalDate.parse(dateString, DateTimeFormatter.ISO_DATE);
                long days = ChronoUnit.DAYS.between(startDate, LocalDate.now());
                sensingStart = String.format(Constants.PATTERN_START_DATE, days);
            } else {
                sensingStart = Constants.DEFAULT_START_DATE;
            }

            String sensingEnd;
            if (commandLine.hasOption(Constants.PARAM_END_DATE)) {
                String dateString = commandLine.getOptionValue(Constants.PARAM_END_DATE);
                LocalDate endDate = LocalDate.parse(dateString, DateTimeFormatter.ISO_DATE);
                long days = ChronoUnit.DAYS.between(endDate, LocalDate.now());
                sensingEnd = String.format(Constants.PATTERN_START_DATE, days);
            } else {
                sensingEnd = Constants.DEFAULT_END_DATE;
            }

            int limit;
            if (commandLine.hasOption(Constants.PARAM_RESULTS_LIMIT)) {
                limit = Integer.parseInt(commandLine.getOptionValue(Constants.PARAM_RESULTS_LIMIT));
            } else {
                limit = Constants.DEFAULT_RESULTS_LIMIT;
            }

            if (commandLine.hasOption(Constants.PARAM_DOWNLOAD_STORE)) {
                String value = commandLine.getOptionValue(Constants.PARAM_DOWNLOAD_STORE);
                if (downloader instanceof SentinelProductDownloader) {
                    ((SentinelProductDownloader) downloader)
                            .setDownloadStore(Enum.valueOf(ProductStore.class, value));
                    logger.info("Products will be downloaded from %s", value);
                } else {
                    logger.warn("Argument --store will be ignored for Landsat8");
                }
            }

            downloader.shouldCompress(commandLine.hasOption(Constants.PARAM_FLAG_COMPRESS));
            downloader.shouldDeleteAfterCompression(commandLine.hasOption(Constants.PARAM_FLAG_DELETE));
            if (commandLine.hasOption(Constants.PARAM_FILL_ANGLES)) {
                if (downloader instanceof SentinelProductDownloader) {
                    ((SentinelProductDownloader) downloader)
                            .setFillMissingAnglesMethod(Enum.valueOf(FillAnglesMethod.class,
                                    commandLine.hasOption(Constants.PARAM_FILL_ANGLES)
                                            ? commandLine.getOptionValue(Constants.PARAM_FILL_ANGLES).toUpperCase()
                                            : FillAnglesMethod.NONE.name()));
                } else {
                    logger.warn("Argument --ma will be ignored for Landsat8");
                }
            }

            int numPoints = areaOfInterest.getNumPoints();
            tiles = tiles.stream().map(t -> t.startsWith("T") ? t.substring(1) : t).collect(Collectors.toSet());
            if (products.size() == 0 && numPoints == 0 && tileMap.getCount() > 0) {
                Rectangle2D rectangle2D = tileMap.boundingBox(tiles);
                areaOfInterest.append(rectangle2D.getX(), rectangle2D.getY());
                areaOfInterest.append(rectangle2D.getMaxX(), rectangle2D.getY());
                areaOfInterest.append(rectangle2D.getMaxX(), rectangle2D.getMaxY());
                areaOfInterest.append(rectangle2D.getX(), rectangle2D.getMaxY());
                areaOfInterest.append(rectangle2D.getX(), rectangle2D.getY());
            }

            numPoints = areaOfInterest.getNumPoints();
            if (products.size() == 0 && numPoints > 0) {
                String searchUrl;
                AbstractSearch searchProvider;
                logger.debug("No product provided, searching on the AOI");
                if (sensorType == SensorType.L8) {
                    logger.debug("Search will be done for Landsat");
                    searchUrl = props.getProperty(Constants.PROPERTY_NAME_LANDSAT_SEARCH_URL,
                            Constants.PROPERTY_NAME_DEFAULT_LANDSAT_SEARCH_URL);
                    if (!NetUtils.isAvailable(searchUrl)) {
                        logger.warn(searchUrl + " is not available!");
                    }
                    searchProvider = new LandsatSearch(searchUrl);
                    if (commandLine.hasOption(Constants.PARAM_START_DATE)) {
                        searchProvider.setSensingStart(commandLine.getOptionValue(Constants.PARAM_START_DATE));
                    }
                    if (commandLine.hasOption(Constants.PARAM_END_DATE)) {
                        searchProvider.setSensingEnd(commandLine.getOptionValue(Constants.PARAM_END_DATE));
                    }
                    if (commandLine.hasOption(Constants.PARAM_TILE_LIST)) {
                        searchProvider.setTiles(tiles);
                    }
                    ((LandsatSearch) searchProvider).limit(limit);
                } else if (!commandLine.hasOption(Constants.PARAM_FLAG_SEARCH_AWS)) {
                    logger.debug("Search will be done on SciHub");
                    searchUrl = props.getProperty(Constants.PROPERTY_NAME_SEARCH_URL,
                            Constants.PROPERTY_DEFAULT_SEARCH_URL);
                    if (!NetUtils.isAvailable(searchUrl)) {
                        logger.warn(searchUrl + " is not available!");
                        searchUrl = props.getProperty(Constants.PROPERTY_NAME_SEARCH_URL_SECONDARY,
                                Constants.PROPERTY_DEFAULT_SEARCH_URL_SECONDARY);
                    }
                    searchProvider = new SciHubSearch(searchUrl);
                    SciHubSearch search = (SciHubSearch) searchProvider;
                    if (user != null && !user.isEmpty() && pwd != null && !pwd.isEmpty()) {
                        search = search.auth(user, pwd);
                    }
                    String interval = "[" + sensingStart + " TO " + sensingEnd + "]";
                    search.filter(Constants.SEARCH_PARAM_INTERVAL, interval).limit(limit);
                    if (commandLine.hasOption(Constants.PARAM_RELATIVE_ORBIT)) {
                        search.filter(Constants.SEARCH_PARAM_RELATIVE_ORBIT_NUMBER,
                                commandLine.getOptionValue(Constants.PARAM_RELATIVE_ORBIT));
                    }
                } else {
                    logger.debug("Search will be done on AWS");
                    searchUrl = props.getProperty(Constants.PROPERTY_NAME_AWS_SEARCH_URL,
                            Constants.PROPERTY_DEFAULT_AWS_SEARCH_URL);
                    searchProvider = new AmazonSearch(searchUrl);
                    searchProvider.setTiles(tiles);
                    Calendar calendar = Calendar.getInstance();
                    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
                    calendar.add(Calendar.DAY_OF_MONTH,
                            Integer.parseInt(sensingStart.replace("NOW", "").replace("DAY", "")));
                    searchProvider.setSensingStart(dateFormat.format(calendar.getTime()));
                    calendar = Calendar.getInstance();
                    String endOffset = sensingEnd.replace("NOW", "").replace("DAY", "");
                    int offset = endOffset.isEmpty() ? 0 : Integer.parseInt(endOffset);
                    calendar.add(Calendar.DAY_OF_MONTH, offset);
                    searchProvider.setSensingEnd(dateFormat.format(calendar.getTime()));
                    if (commandLine.hasOption(Constants.PARAM_RELATIVE_ORBIT)) {
                        searchProvider.setOrbit(
                                Integer.parseInt(commandLine.getOptionValue(Constants.PARAM_RELATIVE_ORBIT)));
                    }
                }
                if (searchProvider.getTiles() == null || searchProvider.getTiles().size() == 0) {
                    searchProvider.setAreaOfInterest(areaOfInterest);
                }
                searchProvider.setClouds(clouds);
                products = searchProvider.execute();
            } else {
                logger.debug("Product name(s) present, no additional search will be performed.");
            }
            if (downloader instanceof SentinelProductDownloader) {
                ((SentinelProductDownloader) downloader).setFilteredTiles(tiles,
                        commandLine.hasOption(Constants.PARAM_FLAG_UNPACKED));
            }
            downloader.setProgressListener(batchProgressListener);
            downloader.setFileProgressListener(fileProgressListener);
            retCode = downloader.downloadProducts(products);
        }
        return retCode;
    }

    public static void setProgressListener(BatchProgressListener progressListener) {
        batchProgressListener = progressListener;
    }

    public static void setFileProgressListener(ProgressListener progressListener) {
        fileProgressListener = progressListener;
    }

    private static void printCommandLine(CommandLine cmd) {
        Logger.getRootLogger().debug("Executing with the following arguments:");
        for (Option option : cmd.getOptions()) {
            if (option.hasArgs()) {
                Logger.getRootLogger().debug(option.getOpt() + "=" + String.join(" ", option.getValues()));
            } else if (option.hasArg()) {
                Logger.getRootLogger().debug(option.getOpt() + "=" + option.getValue());
            } else {
                Logger.getRootLogger().debug(option.getOpt());
            }
        }
    }

    private static String nullIfEmpty(String string) {
        return string != null ? (string.isEmpty() ? null : string) : null;
    }
}