001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.oozie.cli; 019 020import org.apache.commons.cli.MissingOptionException; 021import org.apache.commons.cli.Options; 022import org.apache.commons.cli.GnuParser; 023import org.apache.commons.cli.ParseException; 024import org.apache.commons.cli.CommandLine; 025import org.apache.commons.cli.HelpFormatter; 026import org.apache.commons.cli.UnrecognizedOptionException; 027 028import java.util.Arrays; 029import java.util.Map; 030import java.util.LinkedHashMap; 031import java.text.MessageFormat; 032import java.io.PrintWriter; 033import java.util.HashSet; 034import java.util.Set; 035 036/** 037 * Command line parser based on Apache common-cli 1.x that supports subcommands. 038 */ 039public class CLIParser { 040 private static final String LEFT_PADDING = " "; 041 042 private String cliName; 043 private String[] cliHelp; 044 private Map<String, Options> commands = new LinkedHashMap<String, Options>(); 045 private Map<String, Boolean> commandWithArgs = new LinkedHashMap<String, Boolean>(); 046 private Map<String, String> commandsHelp = new LinkedHashMap<String, String>(); 047 048 /** 049 * Create a parser. 050 * 051 * @param cliName name of the parser, for help purposes. 052 * @param cliHelp help for the CLI. 053 */ 054 public CLIParser(String cliName, String[] cliHelp) { 055 this.cliName = cliName; 056 this.cliHelp = cliHelp; 057 } 058 059 /** 060 * Add a command to the parser. 061 * 062 * @param command comand name. 063 * @param argsHelp command arguments help. 064 * @param commandHelp command description. 065 * @param commandOptions command options. 066 * @param hasArguments 067 */ 068 public void addCommand(String command, String argsHelp, String commandHelp, Options commandOptions, 069 boolean hasArguments) { 070 String helpMsg = argsHelp + ((hasArguments) ? "<ARGS> " : "") + ": " + commandHelp; 071 commandsHelp.put(command, helpMsg); 072 commands.put(command, commandOptions); 073 commandWithArgs.put(command, hasArguments); 074 } 075 076 /** 077 * Bean that represents a parsed command. 078 */ 079 public class Command { 080 private String name; 081 private CommandLine commandLine; 082 083 private Command(String name, CommandLine commandLine) { 084 this.name = name; 085 this.commandLine = commandLine; 086 } 087 088 /** 089 * Return the command name. 090 * 091 * @return the command name. 092 */ 093 public String getName() { 094 return name; 095 } 096 097 /** 098 * Return the command line. 099 * 100 * @return the command line. 101 */ 102 public CommandLine getCommandLine() { 103 return commandLine; 104 } 105 } 106 107 /** 108 * Parse a array of arguments into a command. 109 * 110 * @param args array of arguments. 111 * @return the parsed Command. 112 * @throws ParseException thrown if the arguments could not be parsed. 113 */ 114 public Command parse(String[] args) throws ParseException { 115 if (args.length == 0) { 116 throw new ParseException("missing sub-command"); 117 } 118 else { 119 if (commands.containsKey(args[0])) { 120 GnuParser parser ; 121 String[] minusCommand = new String[args.length - 1]; 122 System.arraycopy(args, 1, minusCommand, 0, minusCommand.length); 123 124 if (args[0].equals(OozieCLI.JOB_CMD)) { 125 validdateArgs(args, minusCommand); 126 parser = new OozieGnuParser(true); 127 } 128 else { 129 parser = new OozieGnuParser(false); 130 } 131 132 return new Command(args[0], parser.parse(commands.get(args[0]), minusCommand, 133 commandWithArgs.get(args[0]))); 134 } 135 else { 136 throw new ParseException(MessageFormat.format("invalid sub-command [{0}]", args[0])); 137 } 138 } 139 } 140 141 public void validdateArgs(final String[] args, String[] minusCommand) throws ParseException { 142 try { 143 GnuParser parser = new OozieGnuParser(false); 144 parser.parse(commands.get(args[0]), minusCommand, commandWithArgs.get(args[0])); 145 } 146 catch (MissingOptionException e) { 147 if (Arrays.toString(args).contains("-dryrun")) { 148 // ignore this, else throw exception 149 //Dryrun is also part of update sub-command. CLI parses dryrun as sub-command and throws 150 //Missing Option Exception, if -dryrun is used as command. It's ok to skip exception only for dryrun. 151 } 152 else { 153 throw e; 154 } 155 } 156 } 157 158 public String shortHelp() { 159 return "use 'help [sub-command]' for help details"; 160 } 161 162 /** 163 * Print the help for the parser to standard output. 164 * 165 * @param commandLine the command line 166 */ 167 public void showHelp(CommandLine commandLine) { 168 PrintWriter pw = new PrintWriter(System.out); 169 pw.println("usage: "); 170 for (String s : cliHelp) { 171 pw.println(LEFT_PADDING + s); 172 } 173 pw.println(); 174 HelpFormatter formatter = new HelpFormatter(); 175 Set<String> commandsToPrint = commands.keySet(); 176 String[] args = commandLine.getArgs(); 177 if (args.length > 0 && commandsToPrint.contains(args[0])) { 178 commandsToPrint = new HashSet<String>(); 179 commandsToPrint.add(args[0]); 180 } 181 for (String comm : commandsToPrint) { 182 Options opts = commands.get(comm); 183 String s = LEFT_PADDING + cliName + " " + comm + " "; 184 if (opts.getOptions().size() > 0) { 185 pw.println(s + "<OPTIONS> " + commandsHelp.get(comm)); 186 formatter.printOptions(pw, 100, opts, s.length(), 3); 187 } 188 else { 189 pw.println(s + commandsHelp.get(comm)); 190 } 191 pw.println(); 192 } 193 pw.flush(); 194 } 195 196 static class OozieGnuParser extends GnuParser { 197 private boolean ignoreMissingOption; 198 199 public OozieGnuParser(final boolean ignoreMissingOption) { 200 this.ignoreMissingOption = ignoreMissingOption; 201 } 202 203 @Override 204 protected void checkRequiredOptions() throws MissingOptionException { 205 if (ignoreMissingOption) { 206 return; 207 } 208 else { 209 super.checkRequiredOptions(); 210 } 211 } 212 } 213 214} 215 216