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