org.apache.falcon.cli.FalconCLI.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.falcon.cli.FalconCLI.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.falcon.cli;

import com.sun.jersey.api.client.ClientHandlerException;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.falcon.LifeCycle;
import org.apache.falcon.ResponseHelper;
import org.apache.falcon.client.FalconCLIException;
import org.apache.falcon.client.FalconClient;
import org.apache.falcon.entity.v0.EntityType;
import org.apache.falcon.entity.v0.SchemaHelper;
import org.apache.falcon.resource.EntityList;
import org.apache.falcon.resource.FeedLookupResult;
import org.apache.falcon.resource.InstanceDependencyResult;
import org.apache.falcon.resource.InstancesResult;
import org.apache.falcon.resource.InstancesSummaryResult;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Falcon Command Line Interface - wraps the RESTful API.
 */
public class FalconCLI {

    public static final AtomicReference<PrintStream> ERR = new AtomicReference<PrintStream>(System.err);
    public static final AtomicReference<PrintStream> OUT = new AtomicReference<PrintStream>(System.out);

    public static final String FALCON_URL = "FALCON_URL";
    public static final String URL_OPTION = "url";
    public static final String VERSION_OPTION = "version";
    public static final String STATUS_OPTION = "status";
    public static final String ADMIN_CMD = "admin";
    public static final String HELP_CMD = "help";
    public static final String METADATA_CMD = "metadata";
    private static final String VERSION_CMD = "version";
    private static final String STACK_OPTION = "stack";

    public static final String ENTITY_CMD = "entity";
    public static final String ENTITY_TYPE_OPT = "type";
    public static final String COLO_OPT = "colo";
    public static final String CLUSTER_OPT = "cluster";
    public static final String ENTITY_NAME_OPT = "name";
    public static final String FILE_PATH_OPT = "file";
    public static final String SUBMIT_OPT = "submit";
    public static final String UPDATE_OPT = "update";
    public static final String SCHEDULE_OPT = "schedule";
    public static final String SUSPEND_OPT = "suspend";
    public static final String RESUME_OPT = "resume";
    public static final String DELETE_OPT = "delete";
    public static final String SUBMIT_AND_SCHEDULE_OPT = "submitAndSchedule";
    public static final String VALIDATE_OPT = "validate";
    public static final String STATUS_OPT = "status";
    public static final String SUMMARY_OPT = "summary";
    public static final String DEFINITION_OPT = "definition";
    public static final String DEPENDENCY_OPT = "dependency";
    public static final String LOOKUP_OPT = "lookup";
    public static final String PATH_OPT = "path";
    public static final String LIST_OPT = "list";
    public static final String TOUCH_OPT = "touch";
    public static final String SKIPDRYRUN_OPT = "skipDryRun";

    public static final String FIELDS_OPT = "fields";
    public static final String FILTER_BY_OPT = "filterBy";
    public static final String TAGS_OPT = "tags";
    public static final String ORDER_BY_OPT = "orderBy";
    public static final String SORT_ORDER_OPT = "sortOrder";
    public static final String OFFSET_OPT = "offset";
    public static final String NUM_RESULTS_OPT = "numResults";
    public static final String NUM_INSTANCES_OPT = "numInstances";
    public static final String NAMESEQ_OPT = "nameseq";
    public static final String TAGKEYS_OPT = "tagkeys";
    public static final String FORCE_RERUN_FLAG = "force";

    public static final String INSTANCE_CMD = "instance";
    public static final String INSTANCE_TIME_OPT = "instanceTime";
    public static final String START_OPT = "start";
    public static final String END_OPT = "end";
    public static final String RUNNING_OPT = "running";
    public static final String KILL_OPT = "kill";
    public static final String RERUN_OPT = "rerun";
    public static final String LOG_OPT = "logs";
    public static final String RUNID_OPT = "runid";
    public static final String CLUSTERS_OPT = "clusters";
    public static final String SOURCECLUSTER_OPT = "sourceClusters";
    public static final String CURRENT_COLO = "current.colo";
    public static final String CLIENT_PROPERTIES = "/client.properties";
    public static final String LIFECYCLE_OPT = "lifecycle";
    public static final String PARARMS_OPT = "params";
    public static final String LISTING_OPT = "listing";
    public static final String TRIAGE_OPT = "triage";

    // Recipe Command
    public static final String RECIPE_CMD = "recipe";
    public static final String RECIPE_NAME = "name";
    public static final String RECIPE_OPERATION = "operation";
    public static final String RECIPE_TOOL_CLASS_NAME = "tool";

    /**
     * Recipe operation enum.
     */
    public static enum RecipeOperation {
        HDFS_REPLICATION, HIVE_DISASTER_RECOVERY
    }

    private final Properties clientProperties;

    public FalconCLI() throws Exception {
        clientProperties = getClientProperties();
    }

    // doAs option
    public static final String DO_AS_OPT = "doAs";

    /**
     * Entry point for the Falcon CLI when invoked from the command line. Upon
     * completion this method exits the JVM with '0' (success) or '-1'
     * (failure).
     *
     * @param args options and arguments for the Falcon CLI.
     */
    public static void main(final String[] args) throws Exception {
        System.exit(new FalconCLI().run(args));
    }

    // TODO help and headers
    private static final String[] FALCON_HELP = {
            "the env variable '" + FALCON_URL + "' is used as default value for the '-" + URL_OPTION + "' option",
            "custom headers for Falcon web services can be specified using '-D" + FalconClient.WS_HEADER_PREFIX
                    + "NAME=VALUE'", };

    /**
     * Run a CLI programmatically.
     * <p/>
     * It does not exit the JVM.
     * <p/>
     * A CLI instance can be used only once.
     *
     * @param args options and arguments for the Oozie CLI.
     * @return '0' (success), '-1' (failure).
     */
    public synchronized int run(final String[] args) {

        CLIParser parser = new CLIParser("falcon", FALCON_HELP);
        FalconMetadataCLI metadataCLI = new FalconMetadataCLI();

        parser.addCommand(ADMIN_CMD, "", "admin operations", createAdminOptions(), true);
        parser.addCommand(HELP_CMD, "", "display usage", new Options(), false);
        parser.addCommand(VERSION_CMD, "", "show client version", new Options(), false);
        parser.addCommand(ENTITY_CMD, "",
                "Entity operations like submit, suspend, resume, delete, status, definition, submitAndSchedule",
                entityOptions(), false);
        parser.addCommand(INSTANCE_CMD, "",
                "Process instances operations like running, status, kill, suspend, resume, rerun, logs",
                instanceOptions(), false);
        parser.addCommand(METADATA_CMD, "", "Metadata operations like list, relations",
                metadataCLI.createMetadataOptions(), true);
        parser.addCommand(RECIPE_CMD, "", "recipe operations", createRecipeOptions(), true);

        try {
            CLIParser.Command command = parser.parse(args);
            int exitValue = 0;
            if (command.getName().equals(HELP_CMD)) {
                parser.showHelp();
            } else {
                CommandLine commandLine = command.getCommandLine();
                String falconUrl = getFalconEndpoint(commandLine);
                FalconClient client = new FalconClient(falconUrl, clientProperties);

                if (command.getName().equals(ADMIN_CMD)) {
                    exitValue = adminCommand(commandLine, client, falconUrl);
                } else if (command.getName().equals(ENTITY_CMD)) {
                    entityCommand(commandLine, client);
                } else if (command.getName().equals(INSTANCE_CMD)) {
                    instanceCommand(commandLine, client);
                } else if (command.getName().equals(METADATA_CMD)) {
                    metadataCLI.metadataCommand(commandLine, client);
                } else if (command.getName().equals(RECIPE_CMD)) {
                    recipeCommand(commandLine, client);
                }
            }
            return exitValue;
        } catch (ParseException ex) {
            ERR.get().println("Invalid sub-command: " + ex.getMessage());
            ERR.get().println();
            ERR.get().println(parser.shortHelp());
            ERR.get().println("Stacktrace:");
            ex.printStackTrace();
            return -1;
        } catch (ClientHandlerException ex) {
            ERR.get().print("Unable to connect to Falcon server, "
                    + "please check if the URL is correct and Falcon server is up and running\n");
            ERR.get().println("Stacktrace:");
            ex.printStackTrace();
            return -1;
        } catch (FalconCLIException e) {
            ERR.get().println("ERROR: " + e.getMessage());
            return -1;
        } catch (Exception ex) {
            ERR.get().println("Stacktrace:");
            ex.printStackTrace();
            return -1;
        }
    }

    private void instanceCommand(CommandLine commandLine, FalconClient client)
            throws FalconCLIException, IOException {
        Set<String> optionsList = new HashSet<String>();
        for (Option option : commandLine.getOptions()) {
            optionsList.add(option.getOpt());
        }

        String result;
        String type = commandLine.getOptionValue(ENTITY_TYPE_OPT);
        String entity = commandLine.getOptionValue(ENTITY_NAME_OPT);
        String instanceTime = commandLine.getOptionValue(INSTANCE_TIME_OPT);
        String start = commandLine.getOptionValue(START_OPT);
        String end = commandLine.getOptionValue(END_OPT);
        String filePath = commandLine.getOptionValue(FILE_PATH_OPT);
        String runId = commandLine.getOptionValue(RUNID_OPT);
        String colo = commandLine.getOptionValue(COLO_OPT);
        String clusters = commandLine.getOptionValue(CLUSTERS_OPT);
        String sourceClusters = commandLine.getOptionValue(SOURCECLUSTER_OPT);
        List<LifeCycle> lifeCycles = getLifeCycle(commandLine.getOptionValue(LIFECYCLE_OPT));
        String filterBy = commandLine.getOptionValue(FILTER_BY_OPT);
        String orderBy = commandLine.getOptionValue(ORDER_BY_OPT);
        String sortOrder = commandLine.getOptionValue(SORT_ORDER_OPT);
        String doAsUser = commandLine.getOptionValue(DO_AS_OPT);
        Integer offset = parseIntegerInput(commandLine.getOptionValue(OFFSET_OPT), 0, "offset");
        Integer numResults = parseIntegerInput(commandLine.getOptionValue(NUM_RESULTS_OPT), null, "numResults");

        colo = getColo(colo);
        String instanceAction = "instance";
        validateSortOrder(sortOrder);
        validateInstanceCommands(optionsList, entity, type, colo);

        if (optionsList.contains(TRIAGE_OPT)) {
            validateNotEmpty(colo, COLO_OPT);
            validateNotEmpty(start, START_OPT);
            validateNotEmpty(type, ENTITY_TYPE_OPT);
            validateEntityTypeForSummary(type);
            validateNotEmpty(entity, ENTITY_NAME_OPT);
            result = client.triage(type, entity, start, colo).toString();
        } else if (optionsList.contains(DEPENDENCY_OPT)) {
            validateNotEmpty(instanceTime, INSTANCE_TIME_OPT);
            InstanceDependencyResult response = client.getInstanceDependencies(type, entity, instanceTime, colo);
            result = ResponseHelper.getString(response);

        } else if (optionsList.contains(RUNNING_OPT)) {
            validateOrderBy(orderBy, instanceAction);
            validateFilterBy(filterBy, instanceAction);
            result = ResponseHelper.getString(client.getRunningInstances(type, entity, colo, lifeCycles, filterBy,
                    orderBy, sortOrder, offset, numResults, doAsUser));
        } else if (optionsList.contains(STATUS_OPT) || optionsList.contains(LIST_OPT)) {
            validateOrderBy(orderBy, instanceAction);
            validateFilterBy(filterBy, instanceAction);
            result = ResponseHelper.getString(client.getStatusOfInstances(type, entity, start, end, colo,
                    lifeCycles, filterBy, orderBy, sortOrder, offset, numResults, doAsUser));
        } else if (optionsList.contains(SUMMARY_OPT)) {
            validateOrderBy(orderBy, "summary");
            validateFilterBy(filterBy, "summary");
            result = ResponseHelper.getString(client.getSummaryOfInstances(type, entity, start, end, colo,
                    lifeCycles, filterBy, orderBy, sortOrder, doAsUser));
        } else if (optionsList.contains(KILL_OPT)) {
            validateNotEmpty(start, START_OPT);
            validateNotEmpty(end, END_OPT);
            result = ResponseHelper.getString(client.killInstances(type, entity, start, end, colo, clusters,
                    sourceClusters, lifeCycles, doAsUser));
        } else if (optionsList.contains(SUSPEND_OPT)) {
            validateNotEmpty(start, START_OPT);
            validateNotEmpty(end, END_OPT);
            result = ResponseHelper.getString(client.suspendInstances(type, entity, start, end, colo, clusters,
                    sourceClusters, lifeCycles, doAsUser));
        } else if (optionsList.contains(RESUME_OPT)) {
            validateNotEmpty(start, START_OPT);
            validateNotEmpty(end, END_OPT);
            result = ResponseHelper.getString(client.resumeInstances(type, entity, start, end, colo, clusters,
                    sourceClusters, lifeCycles, doAsUser));
        } else if (optionsList.contains(RERUN_OPT)) {
            validateNotEmpty(start, START_OPT);
            validateNotEmpty(end, END_OPT);
            boolean isForced = false;
            if (optionsList.contains(FORCE_RERUN_FLAG)) {
                isForced = true;
            }
            result = ResponseHelper.getString(client.rerunInstances(type, entity, start, end, filePath, colo,
                    clusters, sourceClusters, lifeCycles, isForced, doAsUser));
        } else if (optionsList.contains(LOG_OPT)) {
            validateOrderBy(orderBy, instanceAction);
            validateFilterBy(filterBy, instanceAction);
            result = ResponseHelper.getString(client.getLogsOfInstances(type, entity, start, end, colo, runId,
                    lifeCycles, filterBy, orderBy, sortOrder, offset, numResults, doAsUser), runId);
        } else if (optionsList.contains(PARARMS_OPT)) {
            // start time is the nominal time of instance
            result = ResponseHelper
                    .getString(client.getParamsOfInstance(type, entity, start, colo, lifeCycles, doAsUser));
        } else if (optionsList.contains(LISTING_OPT)) {
            result = ResponseHelper.getString(client.getFeedListing(type, entity, start, end, colo, doAsUser));
        } else {
            throw new FalconCLIException("Invalid command");
        }

        OUT.get().println(result);
    }

    private Integer parseIntegerInput(String optionValue, Integer defaultVal, String optionName)
            throws FalconCLIException {
        Integer integer = defaultVal;
        if (optionValue != null) {
            try {
                return Integer.parseInt(optionValue);
            } catch (NumberFormatException e) {
                throw new FalconCLIException(
                        "Input value provided for queryParam \"" + optionName + "\" is not a valid Integer");
            }
        }
        return integer;
    }

    private void validateInstanceCommands(Set<String> optionsList, String entity, String type, String colo)
            throws FalconCLIException {

        validateNotEmpty(entity, ENTITY_NAME_OPT);
        validateNotEmpty(type, ENTITY_TYPE_OPT);
        validateNotEmpty(colo, COLO_OPT);

        if (optionsList.contains(CLUSTERS_OPT)) {
            if (optionsList.contains(RUNNING_OPT) || optionsList.contains(LOG_OPT)
                    || optionsList.contains(STATUS_OPT) || optionsList.contains(SUMMARY_OPT)) {
                throw new FalconCLIException("Invalid argument: clusters");
            }
        }

        if (optionsList.contains(SOURCECLUSTER_OPT)) {
            if (optionsList.contains(RUNNING_OPT) || optionsList.contains(LOG_OPT)
                    || optionsList.contains(STATUS_OPT) || optionsList.contains(SUMMARY_OPT)
                    || !type.equals("feed")) {
                throw new FalconCLIException("Invalid argument: sourceClusters");
            }
        }

        if (optionsList.contains(FORCE_RERUN_FLAG)) {
            if (!optionsList.contains(RERUN_OPT)) {
                throw new FalconCLIException("Force option can be used only with instance rerun");
            }
        }
    }

    private void entityCommand(CommandLine commandLine, FalconClient client)
            throws FalconCLIException, IOException {
        Set<String> optionsList = new HashSet<String>();
        for (Option option : commandLine.getOptions()) {
            optionsList.add(option.getOpt());
        }

        String result = null;
        String entityType = commandLine.getOptionValue(ENTITY_TYPE_OPT);
        String entityName = commandLine.getOptionValue(ENTITY_NAME_OPT);
        String filePath = commandLine.getOptionValue(FILE_PATH_OPT);
        String colo = commandLine.getOptionValue(COLO_OPT);
        String cluster = commandLine.getOptionValue(CLUSTER_OPT);
        String start = commandLine.getOptionValue(START_OPT);
        String end = commandLine.getOptionValue(END_OPT);
        String orderBy = commandLine.getOptionValue(ORDER_BY_OPT);
        String sortOrder = commandLine.getOptionValue(SORT_ORDER_OPT);
        String filterBy = commandLine.getOptionValue(FILTER_BY_OPT);
        String filterTags = commandLine.getOptionValue(TAGS_OPT);
        String nameSubsequence = commandLine.getOptionValue(NAMESEQ_OPT);
        String tagKeywords = commandLine.getOptionValue(TAGKEYS_OPT);
        String fields = commandLine.getOptionValue(FIELDS_OPT);
        String feedInstancePath = commandLine.getOptionValue(PATH_OPT);
        Integer offset = parseIntegerInput(commandLine.getOptionValue(OFFSET_OPT), 0, "offset");
        Integer numResults = parseIntegerInput(commandLine.getOptionValue(NUM_RESULTS_OPT), null, "numResults");
        String doAsUser = commandLine.getOptionValue(DO_AS_OPT);

        Integer numInstances = parseIntegerInput(commandLine.getOptionValue(NUM_INSTANCES_OPT), 7, "numInstances");
        Boolean skipDryRun = null;
        if (optionsList.contains(SKIPDRYRUN_OPT)) {
            skipDryRun = true;
        }

        EntityType entityTypeEnum = null;
        if (optionsList.contains(LIST_OPT)) {
            if (entityType == null) {
                entityType = "";
            }
            if (StringUtils.isNotEmpty(entityType)) {
                String[] types = entityType.split(",");
                for (String type : types) {
                    EntityType.getEnum(type);
                }
            }
        } else {
            validateNotEmpty(entityType, ENTITY_TYPE_OPT);
            entityTypeEnum = EntityType.getEnum(entityType);
        }
        validateSortOrder(sortOrder);
        String entityAction = "entity";

        if (optionsList.contains(SUBMIT_OPT)) {
            validateNotEmpty(filePath, "file");
            validateColo(optionsList);
            result = client.submit(entityType, filePath, doAsUser).getMessage();
        } else if (optionsList.contains(LOOKUP_OPT)) {
            validateNotEmpty(feedInstancePath, PATH_OPT);
            FeedLookupResult resp = client.reverseLookUp(entityType, feedInstancePath, doAsUser);
            result = ResponseHelper.getString(resp);

        } else if (optionsList.contains(UPDATE_OPT)) {
            validateNotEmpty(filePath, "file");
            validateColo(optionsList);
            validateNotEmpty(entityName, ENTITY_NAME_OPT);
            result = client.update(entityType, entityName, filePath, skipDryRun, doAsUser).getMessage();
        } else if (optionsList.contains(SUBMIT_AND_SCHEDULE_OPT)) {
            validateNotEmpty(filePath, "file");
            validateColo(optionsList);
            result = client.submitAndSchedule(entityType, filePath, skipDryRun, doAsUser).getMessage();
        } else if (optionsList.contains(VALIDATE_OPT)) {
            validateNotEmpty(filePath, "file");
            validateColo(optionsList);
            result = client.validate(entityType, filePath, skipDryRun, doAsUser).getMessage();
        } else if (optionsList.contains(SCHEDULE_OPT)) {
            validateNotEmpty(entityName, ENTITY_NAME_OPT);
            colo = getColo(colo);
            result = client.schedule(entityTypeEnum, entityName, colo, skipDryRun, doAsUser).getMessage();
        } else if (optionsList.contains(SUSPEND_OPT)) {
            validateNotEmpty(entityName, ENTITY_NAME_OPT);
            colo = getColo(colo);
            result = client.suspend(entityTypeEnum, entityName, colo, doAsUser).getMessage();
        } else if (optionsList.contains(RESUME_OPT)) {
            validateNotEmpty(entityName, ENTITY_NAME_OPT);
            colo = getColo(colo);
            result = client.resume(entityTypeEnum, entityName, colo, doAsUser).getMessage();
        } else if (optionsList.contains(DELETE_OPT)) {
            validateColo(optionsList);
            validateNotEmpty(entityName, ENTITY_NAME_OPT);
            result = client.delete(entityTypeEnum, entityName, doAsUser).getMessage();
        } else if (optionsList.contains(STATUS_OPT)) {
            validateNotEmpty(entityName, ENTITY_NAME_OPT);
            colo = getColo(colo);
            result = client.getStatus(entityTypeEnum, entityName, colo, doAsUser).getMessage();
        } else if (optionsList.contains(DEFINITION_OPT)) {
            validateColo(optionsList);
            validateNotEmpty(entityName, ENTITY_NAME_OPT);
            result = client.getDefinition(entityType, entityName, doAsUser).toString();
        } else if (optionsList.contains(DEPENDENCY_OPT)) {
            validateColo(optionsList);
            validateNotEmpty(entityName, ENTITY_NAME_OPT);
            result = client.getDependency(entityType, entityName, doAsUser).toString();
        } else if (optionsList.contains(LIST_OPT)) {
            validateColo(optionsList);
            validateEntityFields(fields);
            validateOrderBy(orderBy, entityAction);
            validateFilterBy(filterBy, entityAction);
            EntityList entityList = client.getEntityList(entityType, fields, nameSubsequence, tagKeywords, filterBy,
                    filterTags, orderBy, sortOrder, offset, numResults, doAsUser);
            result = entityList != null ? entityList.toString() : "No entity of type (" + entityType + ") found.";
        } else if (optionsList.contains(SUMMARY_OPT)) {
            validateEntityTypeForSummary(entityType);
            validateNotEmpty(cluster, CLUSTER_OPT);
            validateEntityFields(fields);
            validateFilterBy(filterBy, entityAction);
            validateOrderBy(orderBy, entityAction);
            result = ResponseHelper.getString(client.getEntitySummary(entityType, cluster, start, end, fields,
                    filterBy, filterTags, orderBy, sortOrder, offset, numResults, numInstances, doAsUser));
        } else if (optionsList.contains(TOUCH_OPT)) {
            validateNotEmpty(entityName, ENTITY_NAME_OPT);
            colo = getColo(colo);
            result = client.touch(entityType, entityName, colo, skipDryRun, doAsUser).getMessage();
        } else if (optionsList.contains(HELP_CMD)) {
            OUT.get().println("Falcon Help");
        } else {
            throw new FalconCLIException("Invalid command");
        }
        OUT.get().println(result);
    }

    private void validateEntityTypeForSummary(String type) throws FalconCLIException {
        EntityType entityType = EntityType.getEnum(type);
        if (!entityType.isSchedulable()) {
            throw new FalconCLIException("Invalid entity type " + entityType
                    + " for EntitySummary API. Valid options are feed or process");
        }
    }

    private void validateNotEmpty(String paramVal, String paramName) throws FalconCLIException {
        if (StringUtils.isEmpty(paramVal)) {
            throw new FalconCLIException("Missing argument : " + paramName);
        }
    }

    private void validateSortOrder(String sortOrder) throws FalconCLIException {
        if (!StringUtils.isEmpty(sortOrder)) {
            if (!sortOrder.equalsIgnoreCase("asc") && !sortOrder.equalsIgnoreCase("desc")) {
                throw new FalconCLIException(
                        "Value for param sortOrder should be \"asc\" or \"desc\". It is  : " + sortOrder);
            }
        }
    }

    private String getColo(String colo) throws FalconCLIException, IOException {
        if (colo == null) {
            Properties prop = getClientProperties();
            colo = prop.getProperty(CURRENT_COLO, "*");
        }
        return colo;
    }

    private void validateColo(Set<String> optionsList) throws FalconCLIException {

        if (optionsList.contains(COLO_OPT)) {
            throw new FalconCLIException("Invalid argument : " + COLO_OPT);
        }
    }

    private void validateEntityFields(String fields) throws FalconCLIException {
        if (StringUtils.isEmpty(fields)) {
            return;
        }
        String[] fieldsList = fields.split(",");
        for (String s : fieldsList) {
            try {
                EntityList.EntityFieldList.valueOf(s.toUpperCase());
            } catch (IllegalArgumentException ie) {
                throw new FalconCLIException("Invalid fields argument : " + FIELDS_OPT);
            }
        }
    }

    private void validateFilterBy(String filterBy, String filterType) throws FalconCLIException {
        if (StringUtils.isEmpty(filterBy)) {
            return;
        }
        String[] filterSplits = filterBy.split(",");
        for (String s : filterSplits) {
            String[] tempKeyVal = s.split(":", 2);
            try {
                if (filterType.equals("entity")) {
                    EntityList.EntityFilterByFields.valueOf(tempKeyVal[0].toUpperCase());
                } else if (filterType.equals("instance")) {
                    InstancesResult.InstanceFilterFields.valueOf(tempKeyVal[0].toUpperCase());
                } else if (filterType.equals("summary")) {
                    InstancesSummaryResult.InstanceSummaryFilterFields.valueOf(tempKeyVal[0].toUpperCase());
                } else {
                    throw new IllegalArgumentException("Invalid API call");
                }
            } catch (IllegalArgumentException ie) {
                throw new FalconCLIException("Invalid filterBy argument : " + FILTER_BY_OPT);
            }
        }
    }

    private void validateOrderBy(String orderBy, String action) throws FalconCLIException {
        if (StringUtils.isEmpty(orderBy)) {
            return;
        }
        if (action.equals("instance")) {
            if (Arrays.asList(new String[] { "status", "cluster", "starttime", "endtime" })
                    .contains(orderBy.toLowerCase())) {
                return;
            }
        } else if (action.equals("entity")) {
            if (Arrays.asList(new String[] { "type", "name" }).contains(orderBy.toLowerCase())) {
                return;
            }
        } else if (action.equals("summary")) {
            if (Arrays.asList(new String[] { "cluster" }).contains(orderBy.toLowerCase())) {
                return;
            }
        }
        throw new FalconCLIException("Invalid orderBy argument : " + orderBy);
    }

    private Date parseDateString(String time) throws FalconCLIException {
        if (time != null && !time.isEmpty()) {
            try {
                return SchemaHelper.parseDateUTC(time);
            } catch (Exception e) {
                throw new FalconCLIException("Time " + time + " is not valid", e);
            }
        }
        return null;
    }

    private Options createAdminOptions() {
        Options adminOptions = new Options();
        Option url = new Option(URL_OPTION, true, "Falcon URL");
        adminOptions.addOption(url);

        OptionGroup group = new OptionGroup();
        Option status = new Option(STATUS_OPTION, false, "show the current system status");
        Option version = new Option(VERSION_OPTION, false, "show Falcon server build version");
        Option stack = new Option(STACK_OPTION, false, "show the thread stack dump");
        Option doAs = new Option(DO_AS_OPT, true, "doAs user");
        Option help = new Option("help", false, "show Falcon help");
        group.addOption(status);
        group.addOption(version);
        group.addOption(stack);
        group.addOption(help);

        adminOptions.addOptionGroup(group);
        adminOptions.addOption(doAs);
        return adminOptions;
    }

    private Options entityOptions() {

        Options entityOptions = new Options();

        Option submit = new Option(SUBMIT_OPT, false, "Submits an entity xml to Falcon");
        Option update = new Option(UPDATE_OPT, false, "Updates an existing entity xml");
        Option schedule = new Option(SCHEDULE_OPT, false, "Schedules a submited entity in Falcon");
        Option suspend = new Option(SUSPEND_OPT, false, "Suspends a running entity in Falcon");
        Option resume = new Option(RESUME_OPT, false, "Resumes a suspended entity in Falcon");
        Option delete = new Option(DELETE_OPT, false,
                "Deletes an entity in Falcon, and kills its instance from workflow engine");
        Option submitAndSchedule = new Option(SUBMIT_AND_SCHEDULE_OPT, false,
                "Submits and entity to Falcon and schedules it immediately");
        Option validate = new Option(VALIDATE_OPT, false, "Validates an entity based on the entity type");
        Option status = new Option(STATUS_OPT, false, "Gets the status of entity");
        Option definition = new Option(DEFINITION_OPT, false, "Gets the Definition of entity");
        Option dependency = new Option(DEPENDENCY_OPT, false, "Gets the dependencies of entity");
        Option list = new Option(LIST_OPT, false, "List entities registered for a type");
        Option lookup = new Option(LOOKUP_OPT, false, "Lookup a feed given its instance's path");
        Option entitySummary = new Option(SUMMARY_OPT, false, "Get summary of instances for list of entities");
        Option touch = new Option(TOUCH_OPT, false,
                "Force update the entity in workflow engine(even without any changes to entity)");

        OptionGroup group = new OptionGroup();
        group.addOption(submit);
        group.addOption(update);
        group.addOption(schedule);
        group.addOption(suspend);
        group.addOption(resume);
        group.addOption(delete);
        group.addOption(submitAndSchedule);
        group.addOption(validate);
        group.addOption(status);
        group.addOption(definition);
        group.addOption(dependency);
        group.addOption(list);
        group.addOption(lookup);
        group.addOption(entitySummary);
        group.addOption(touch);

        Option url = new Option(URL_OPTION, true, "Falcon URL");
        Option entityType = new Option(ENTITY_TYPE_OPT, true, "Entity type, can be cluster, feed or process xml");
        Option filePath = new Option(FILE_PATH_OPT, true, "Path to entity xml file");
        Option entityName = new Option(ENTITY_NAME_OPT, true, "Entity type, can be cluster, feed or process xml");
        Option start = new Option(START_OPT, true, "Start time is optional for summary");
        Option end = new Option(END_OPT, true, "End time is optional for summary");
        Option colo = new Option(COLO_OPT, true, "Colo name");
        Option cluster = new Option(CLUSTER_OPT, true, "Cluster name");
        colo.setRequired(false);
        Option fields = new Option(FIELDS_OPT, true, "Entity fields to show for a request");
        Option filterBy = new Option(FILTER_BY_OPT, true, "Filter returned entities by the specified status");
        Option filterTags = new Option(TAGS_OPT, true, "Filter returned entities by the specified tags");
        Option nameSubsequence = new Option(NAMESEQ_OPT, true, "Subsequence of entity name");
        Option tagKeywords = new Option(TAGKEYS_OPT, true, "Keywords in tags");
        Option orderBy = new Option(ORDER_BY_OPT, true, "Order returned entities by this field");
        Option sortOrder = new Option(SORT_ORDER_OPT, true, "asc or desc order for results");
        Option offset = new Option(OFFSET_OPT, true, "Start returning entities from this offset");
        Option numResults = new Option(NUM_RESULTS_OPT, true, "Number of results to return per request");
        Option numInstances = new Option(NUM_INSTANCES_OPT, true,
                "Number of instances to return per entity summary request");
        Option path = new Option(PATH_OPT, true, "Path for a feed's instance");
        Option skipDryRun = new Option(SKIPDRYRUN_OPT, false, "skip dry run in workflow engine");
        Option doAs = new Option(DO_AS_OPT, true, "doAs user");

        entityOptions.addOption(url);
        entityOptions.addOption(path);
        entityOptions.addOptionGroup(group);
        entityOptions.addOption(entityType);
        entityOptions.addOption(entityName);
        entityOptions.addOption(filePath);
        entityOptions.addOption(colo);
        entityOptions.addOption(cluster);
        entityOptions.addOption(start);
        entityOptions.addOption(end);
        entityOptions.addOption(fields);
        entityOptions.addOption(filterBy);
        entityOptions.addOption(filterTags);
        entityOptions.addOption(nameSubsequence);
        entityOptions.addOption(tagKeywords);
        entityOptions.addOption(orderBy);
        entityOptions.addOption(sortOrder);
        entityOptions.addOption(offset);
        entityOptions.addOption(numResults);
        entityOptions.addOption(numInstances);
        entityOptions.addOption(skipDryRun);
        entityOptions.addOption(doAs);

        return entityOptions;
    }

    private Options instanceOptions() {

        Options instanceOptions = new Options();

        Option running = new Option(RUNNING_OPT, false, "Gets running process instances for a given process");
        Option list = new Option(LIST_OPT, false,
                "Gets all instances for a given process in the range start time and optional end time");
        Option status = new Option(STATUS_OPT, false,
                "Gets status of process instances for a given process in the range start time and optional end time");
        Option summary = new Option(SUMMARY_OPT, false,
                "Gets summary of instances for a given process in the range start time and optional end time");
        Option kill = new Option(KILL_OPT, false,
                "Kills active process instances for a given process in the range start time and optional end time");
        Option suspend = new Option(SUSPEND_OPT, false,
                "Suspends active process instances for a given process in the range start time and optional end time");
        Option resume = new Option(RESUME_OPT, false, "Resumes suspended process instances for a given process "
                + "in the range start time and optional end time");
        Option rerun = new Option(RERUN_OPT, false,
                "Reruns process instances for a given process in the range start time and "
                        + "optional end time and overrides properties present in job.properties file");

        Option logs = new Option(LOG_OPT, false, "Logs print the logs for process instances for a given process in "
                + "the range start time and optional end time");

        Option params = new Option(PARARMS_OPT, false,
                "Displays the workflow parameters for a given instance of specified nominal time"
                        + "start time represents nominal time and end time is not considered");

        Option listing = new Option(LISTING_OPT, false,
                "Displays feed listing and their status between a start and end time range.");

        Option dependency = new Option(DEPENDENCY_OPT, false,
                "Displays dependent instances for a specified instance.");

        Option triage = new Option(TRIAGE_OPT, false,
                "Triage a feed or process instance and find the failures in it's lineage.");

        OptionGroup group = new OptionGroup();
        group.addOption(running);
        group.addOption(list);
        group.addOption(status);
        group.addOption(summary);
        group.addOption(kill);
        group.addOption(resume);
        group.addOption(suspend);
        group.addOption(resume);
        group.addOption(rerun);
        group.addOption(logs);
        group.addOption(params);
        group.addOption(listing);
        group.addOption(dependency);
        group.addOption(triage);

        Option url = new Option(URL_OPTION, true, "Falcon URL");
        Option start = new Option(START_OPT, true,
                "Start time is required for commands, status, kill, suspend, resume and re-run"
                        + "and it is nominal time while displaying workflow params");
        Option end = new Option(END_OPT, true,
                "End time is optional for commands, status, kill, suspend, resume and re-run; "
                        + "if not specified then current time is considered as end time");
        Option runid = new Option(RUNID_OPT, true,
                "Instance runid  is optional and user can specify the runid, defaults to 0");
        Option clusters = new Option(CLUSTERS_OPT, true,
                "clusters is optional for commands kill, suspend and resume, "
                        + "should not be specified for other commands");
        Option sourceClusters = new Option(SOURCECLUSTER_OPT, true,
                " source cluster is optional for commands kill, suspend and resume, "
                        + "should not be specified for other commands (required for only feed)");
        Option filePath = new Option(FILE_PATH_OPT, true,
                "Path to job.properties file is required for rerun command, "
                        + "it should contain name=value pair for properties to override for rerun");
        Option entityType = new Option(ENTITY_TYPE_OPT, true, "Entity type, can be feed or process xml");
        Option entityName = new Option(ENTITY_NAME_OPT, true, "Entity name, can be feed or process name");
        Option colo = new Option(COLO_OPT, true, "Colo on which the cmd has to be executed");
        Option lifecycle = new Option(LIFECYCLE_OPT, true,
                "describes life cycle of entity , for feed it can be replication/retention "
                        + "and for process it can be execution");
        Option filterBy = new Option(FILTER_BY_OPT, true, "Filter returned instances by the specified fields");
        Option orderBy = new Option(ORDER_BY_OPT, true, "Order returned instances by this field");
        Option sortOrder = new Option(SORT_ORDER_OPT, true, "asc or desc order for results");
        Option offset = new Option(OFFSET_OPT, true, "Start returning instances from this offset");
        Option numResults = new Option(NUM_RESULTS_OPT, true, "Number of results to return per request");
        Option forceRerun = new Option(FORCE_RERUN_FLAG, false,
                "Flag to forcefully rerun entire workflow of an instance");
        Option doAs = new Option(DO_AS_OPT, true, "doAs user");

        Option instanceTime = new Option(INSTANCE_TIME_OPT, true, "Time for an instance");

        instanceOptions.addOption(url);
        instanceOptions.addOptionGroup(group);
        instanceOptions.addOption(start);
        instanceOptions.addOption(end);
        instanceOptions.addOption(filePath);
        instanceOptions.addOption(entityType);
        instanceOptions.addOption(entityName);
        instanceOptions.addOption(runid);
        instanceOptions.addOption(clusters);
        instanceOptions.addOption(sourceClusters);
        instanceOptions.addOption(colo);
        instanceOptions.addOption(lifecycle);
        instanceOptions.addOption(filterBy);
        instanceOptions.addOption(offset);
        instanceOptions.addOption(orderBy);
        instanceOptions.addOption(sortOrder);
        instanceOptions.addOption(numResults);
        instanceOptions.addOption(forceRerun);
        instanceOptions.addOption(doAs);
        instanceOptions.addOption(instanceTime);

        return instanceOptions;
    }

    private Options createRecipeOptions() {
        Options recipeOptions = new Options();
        Option url = new Option(URL_OPTION, true, "Falcon URL");
        recipeOptions.addOption(url);

        Option recipeFileOpt = new Option(RECIPE_NAME, true, "recipe name");
        recipeOptions.addOption(recipeFileOpt);

        Option recipeToolClassName = new Option(RECIPE_TOOL_CLASS_NAME, true, "recipe class");
        recipeOptions.addOption(recipeToolClassName);

        Option recipeOperation = new Option(RECIPE_OPERATION, true, "recipe operation");
        recipeOptions.addOption(recipeOperation);

        Option skipDryRunOperation = new Option(SKIPDRYRUN_OPT, false, "skip dryrun operation");
        recipeOptions.addOption(skipDryRunOperation);

        Option doAs = new Option(DO_AS_OPT, true, "doAs user");
        recipeOptions.addOption(doAs);

        return recipeOptions;
    }

    protected String getFalconEndpoint(CommandLine commandLine) throws FalconCLIException, IOException {
        String url = commandLine.getOptionValue(URL_OPTION);
        if (url == null) {
            url = System.getenv(FALCON_URL);
        }
        if (url == null) {
            if (clientProperties.containsKey("falcon.url")) {
                url = clientProperties.getProperty("falcon.url");
            }
        }
        if (url == null) {
            throw new FalconCLIException(
                    "Failed to get falcon url from cmdline, or environment or client properties");
        }

        return url;
    }

    private int adminCommand(CommandLine commandLine, FalconClient client, String falconUrl)
            throws FalconCLIException, IOException {
        String result;
        Set<String> optionsList = new HashSet<String>();
        for (Option option : commandLine.getOptions()) {
            optionsList.add(option.getOpt());
        }

        String doAsUser = commandLine.getOptionValue(DO_AS_OPT);

        if (optionsList.contains(STACK_OPTION)) {
            result = client.getThreadDump(doAsUser);
            OUT.get().println(result);
        }
        int exitValue = 0;
        if (optionsList.contains(STATUS_OPTION)) {
            try {
                int status = client.getStatus(doAsUser);
                if (status != 200) {
                    ERR.get().println("Falcon server is not fully operational (on " + falconUrl + "). "
                            + "Please check log files.");
                    exitValue = status;
                } else {
                    OUT.get().println("Falcon server is running (on " + falconUrl + ")");
                }
            } catch (Exception e) {
                ERR.get().println("Falcon server doesn't seem to be running on " + falconUrl);
                exitValue = -1;
            }
        } else if (optionsList.contains(VERSION_OPTION)) {
            result = client.getVersion(doAsUser);
            OUT.get().println("Falcon server build version: " + result);
        } else if (optionsList.contains(HELP_CMD)) {
            OUT.get().println("Falcon Help");
        }

        return exitValue;
    }

    private Properties getClientProperties() throws IOException {
        InputStream inputStream = null;
        try {
            inputStream = FalconCLI.class.getResourceAsStream(CLIENT_PROPERTIES);
            Properties prop = new Properties();
            if (inputStream != null) {
                prop.load(inputStream);
            }
            return prop;
        } finally {
            IOUtils.closeQuietly(inputStream);
        }
    }

    public static List<LifeCycle> getLifeCycle(String lifeCycleValue) throws FalconCLIException {

        if (lifeCycleValue != null) {
            String[] lifeCycleValues = lifeCycleValue.split(",");
            List<LifeCycle> lifeCycles = new ArrayList<LifeCycle>();
            try {
                for (String lifeCycle : lifeCycleValues) {
                    lifeCycles.add(LifeCycle.valueOf(lifeCycle.toUpperCase().trim()));
                }
            } catch (IllegalArgumentException e) {
                throw new FalconCLIException("Invalid life cycle values: " + lifeCycles, e);
            }
            return lifeCycles;
        }
        return null;
    }

    private void recipeCommand(CommandLine commandLine, FalconClient client) throws FalconCLIException {
        Set<String> optionsList = new HashSet<String>();
        for (Option option : commandLine.getOptions()) {
            optionsList.add(option.getOpt());
        }

        String recipeName = commandLine.getOptionValue(RECIPE_NAME);
        String recipeToolClass = commandLine.getOptionValue(RECIPE_TOOL_CLASS_NAME);
        String recipeOperation = commandLine.getOptionValue(RECIPE_OPERATION);
        String doAsUser = commandLine.getOptionValue(DO_AS_OPT);

        validateNotEmpty(recipeName, RECIPE_NAME);
        validateNotEmpty(recipeOperation, RECIPE_OPERATION);
        validateRecipeOperations(recipeOperation);
        Boolean skipDryRun = null;
        if (optionsList.contains(SKIPDRYRUN_OPT)) {
            skipDryRun = true;
        }

        String result = client.submitRecipe(recipeName, recipeToolClass, recipeOperation, skipDryRun, doAsUser)
                .toString();
        OUT.get().println(result);
    }

    private static void validateRecipeOperations(String recipeOperation) throws FalconCLIException {
        for (RecipeOperation operation : RecipeOperation.values()) {
            if (operation.toString().equalsIgnoreCase(recipeOperation)) {
                return;
            }
        }
        throw new FalconCLIException(
                "Allowed Recipe operations: " + java.util.Arrays.asList((RecipeOperation.values())));
    }
}