| 1 | /* |
| 2 | * Copyright 2005-2006 The RbUtils Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | * |
| 16 | */ |
| 17 | |
| 18 | // $Id: AbstractUtility.java,v 1.2 2006/11/18 13:33:38 moishi Exp $ |
| 19 | |
| 20 | package org.ktc.rbutils.rb; |
| 21 | |
| 22 | import java.io.File; |
| 23 | import java.io.FileNotFoundException; |
| 24 | import java.io.IOException; |
| 25 | import java.io.PrintWriter; |
| 26 | import java.util.ArrayList; |
| 27 | import java.util.Collection; |
| 28 | import java.util.Iterator; |
| 29 | import java.util.LinkedList; |
| 30 | import java.util.List; |
| 31 | |
| 32 | import org.apache.commons.cli.CommandLine; |
| 33 | import org.apache.commons.cli.CommandLineParser; |
| 34 | import org.apache.commons.cli.HelpFormatter; |
| 35 | import org.apache.commons.cli.MissingArgumentException; |
| 36 | import org.apache.commons.cli.MissingOptionException; |
| 37 | import org.apache.commons.cli.Option; |
| 38 | import org.apache.commons.cli.Options; |
| 39 | import org.apache.commons.cli.ParseException; |
| 40 | import org.apache.commons.cli.PosixParser; |
| 41 | import org.apache.commons.lang.StringUtils; |
| 42 | import org.ktc.rbutils.api.RbUtilsException; |
| 43 | import org.ktc.rbutils.api.UnSpecifiedFileException; |
| 44 | import org.ktc.rbutils.api.audit.Logger; |
| 45 | import org.ktc.rbutils.api.file.ValidateFile; |
| 46 | |
| 47 | /** |
| 48 | * Provides common functionnalities to the command line utilities. |
| 49 | * @since RbUtils 0.9.3.3 |
| 50 | * @version $Revision: 1.2 $ |
| 51 | * @author moishi |
| 52 | */ |
| 53 | public abstract class AbstractUtility implements Utility { |
| 54 | /** The options to the command line. */ |
| 55 | private static final Options COMMON_OPTS = new Options(); |
| 56 | |
| 57 | /** The e option (for properties files extension). */ |
| 58 | protected static final String OPTION_EXTENSION = "e"; |
| 59 | /** Error mesage when more than one extension is specified. */ |
| 60 | protected static final String ERROR_MSG_EXTENSION_NBR = "Must specify at most one extension"; |
| 61 | |
| 62 | /** The f option (for files to be processed). */ |
| 63 | protected static final String OPTION_FILE = "f"; |
| 64 | |
| 65 | /** The l option (for loggers to be registered). */ |
| 66 | protected static final String OPTION_LOG = "l"; |
| 67 | /** Separator used to separate logger type and logging file. */ |
| 68 | public static final char SEP_LOG_OPTION = ';'; |
| 69 | |
| 70 | /** The r option (for root directory). */ |
| 71 | protected static final String OPTION_ROOT = "r"; |
| 72 | |
| 73 | /** Error mesage when more than one root directory is specified. */ |
| 74 | protected static final String ERROR_MSG_ROOT_NBR = "Must specify one " |
| 75 | + "and only one root directory"; |
| 76 | |
| 77 | /** |
| 78 | * Displayed message for user (to be used by subclasses when error occurs on parameter parsing). |
| 79 | */ |
| 80 | protected static final String MSG_MISSING_REQUIRED_OPTION = "Missing option: "; |
| 81 | |
| 82 | /** |
| 83 | * Name of the RbUtils task of this utility. Subclasses <b>MUST</b> set this field in their |
| 84 | * constructor. |
| 85 | */ |
| 86 | protected String taskName; |
| 87 | /** |
| 88 | * Options of this utility. Subclasses <b>MUST</b> set this field in their constructor. |
| 89 | */ |
| 90 | protected Options options; |
| 91 | |
| 92 | /** |
| 93 | * Internal arguments to be passed to the utility. Subclasses <b>MUST</b> set this field in |
| 94 | * their constructor. |
| 95 | */ |
| 96 | protected String[] internalArgs; |
| 97 | |
| 98 | /** The root directory to be used by this utility. */ |
| 99 | protected File root; |
| 100 | |
| 101 | /** Command line of this utility. */ |
| 102 | protected CommandLine cmdLine; |
| 103 | |
| 104 | /** Header for required field used when the usage of the utility is displayed. */ |
| 105 | protected static final String REQUIRED_HEADER = "[required] "; |
| 106 | static { |
| 107 | final Option rootOption = new Option(OPTION_ROOT, "root", true, REQUIRED_HEADER |
| 108 | + "the directory to be traversed for properties source files."); |
| 109 | rootOption.setRequired(true); |
| 110 | // TODO to be used when the Option clone method will work for this field |
| 111 | // rootOption.setArgName("root"); |
| 112 | COMMON_OPTS.addOption(rootOption); |
| 113 | |
| 114 | COMMON_OPTS.addOption(OPTION_EXTENSION, "extension", true, |
| 115 | "the extension of the properties source files." |
| 116 | + " Don't include the dot in the extension." |
| 117 | + " Defaults to properties."); |
| 118 | |
| 119 | final String logMsg = "the type of logging: (plain|xml|quiet)." |
| 120 | + " Defaults to plain (outputs are displayed in the standard output)." |
| 121 | + " To specify a logging file, put a ';' after the type of logging and specified " |
| 122 | + "the full path to the logging file. This is required for xml logging."; |
| 123 | final Option logOption = new Option(OPTION_LOG, "log", true, logMsg); |
| 124 | logOption.setArgs(2); |
| 125 | logOption.setValueSeparator(SEP_LOG_OPTION); |
| 126 | COMMON_OPTS.addOption(logOption); |
| 127 | |
| 128 | COMMON_OPTS.addOption(OPTION_FILE, "file", true, |
| 129 | "the relative path from root to a properties file to be processed."); |
| 130 | } |
| 131 | |
| 132 | /** |
| 133 | * {@inheritDoc} |
| 134 | */ |
| 135 | public void parseParameters() throws ParseException { |
| 136 | final CommandLineParser clp = new PosixParser(); |
| 137 | cmdLine = clp.parse(options, internalArgs, true); |
| 138 | } |
| 139 | |
| 140 | /** |
| 141 | * {@inheritDoc} |
| 142 | */ |
| 143 | public File getRootDir() throws RbUtilsException, FileNotFoundException { |
| 144 | final File uniqueDir = getUniqueDir(OPTION_ROOT, ERROR_MSG_ROOT_NBR); |
| 145 | |
| 146 | root = uniqueDir; |
| 147 | return uniqueDir; |
| 148 | } |
| 149 | |
| 150 | /** |
| 151 | * Parses the command line options in order to get a directory which must have been specified |
| 152 | * only once. |
| 153 | * @param optionName the name of the option to be parsed. |
| 154 | * @param errorMsg the error message to be used if a RbUtilsException is thrown. |
| 155 | * @return the directory. |
| 156 | * @throws RbUtilsException if the option is specified more than once. |
| 157 | * @throws FileNotFoundException if the directory does not exist in the file system. |
| 158 | * @throws org.ktc.rbutils.api.file.NotDirectoryException if the -r value specifies a file in |
| 159 | * the file system instead of a directory. |
| 160 | */ |
| 161 | protected File getUniqueDir(final String optionName, final String errorMsg) |
| 162 | throws RbUtilsException, FileNotFoundException |
| 163 | { |
| 164 | final String rootValue = getUniqueOptionValue(optionName, errorMsg, true); |
| 165 | final File internalRoot = new File(rootValue); |
| 166 | |
| 167 | ValidateFile.isDirectory(internalRoot); |
| 168 | |
| 169 | return internalRoot; |
| 170 | } |
| 171 | |
| 172 | /** |
| 173 | * Parses the command line options in order to get an option value which must have been |
| 174 | * specified at most once. |
| 175 | * @param optionName the name of the option to be parsed. |
| 176 | * @param errorMsg the error message to be used if a RbUtilsException is thrown. |
| 177 | * @param isRequired <code>true</code> if the optionName must have been specified. |
| 178 | * @return the option value. <code>null</code> if no specified option. |
| 179 | * @throws RbUtilsException if the option is specified more than once. |
| 180 | */ |
| 181 | protected String getUniqueOptionValue(final String optionName, final String errorMsg, |
| 182 | final boolean isRequired) throws RbUtilsException |
| 183 | { |
| 184 | String value = null; |
| 185 | if (!cmdLine.hasOption(optionName)) { |
| 186 | if (isRequired) { |
| 187 | throw new RbUtilsException(errorMsg); |
| 188 | } |
| 189 | // if it is not required, we only exit from this method with a null value |
| 190 | } |
| 191 | else { |
| 192 | final String[] optionValues = cmdLine.getOptionValues(optionName); |
| 193 | final int optionNbr = optionValues.length; |
| 194 | // we only accept one value for the optionName option |
| 195 | // TODO this must be done by the parser (set option property) |
| 196 | if (optionNbr > 1 || (isRequired && optionNbr == 0)) { |
| 197 | throw new RbUtilsException(errorMsg); |
| 198 | } |
| 199 | value = optionValues[0]; |
| 200 | } |
| 201 | return value; |
| 202 | } |
| 203 | |
| 204 | /** |
| 205 | * {@inheritDoc} |
| 206 | */ |
| 207 | public List getFilesToBeProcessed() throws FileNotFoundException { |
| 208 | final List files = new LinkedList(); |
| 209 | if (cmdLine.hasOption(OPTION_FILE)) { |
| 210 | final String[] fileValues = cmdLine.getOptionValues(OPTION_FILE); |
| 211 | for (int i = 0; i < fileValues.length; i++) { |
| 212 | final String relPath = fileValues[i]; |
| 213 | final File file = new File(root, relPath); |
| 214 | |
| 215 | ValidateFile.isFile(file); |
| 216 | files.add(file); |
| 217 | } |
| 218 | } |
| 219 | |
| 220 | return files; |
| 221 | } |
| 222 | |
| 223 | /** |
| 224 | * {@inheritDoc} |
| 225 | */ |
| 226 | public String getExtension() throws RbUtilsException { |
| 227 | return getUniqueOptionValue(OPTION_EXTENSION, ERROR_MSG_EXTENSION_NBR, false); |
| 228 | } |
| 229 | |
| 230 | /** |
| 231 | * {@inheritDoc} |
| 232 | */ |
| 233 | public Collection getLoggers() throws UnSpecifiedFileException, IOException { |
| 234 | // TODO improve parsing in order to avoid 2nd arg processing |
| 235 | // look in the cli doc or developp another parser |
| 236 | final Collection loggers = new ArrayList(); |
| 237 | |
| 238 | if (cmdLine.hasOption(OPTION_LOG)) { |
| 239 | // System.out.println("getLoggers"); |
| 240 | final String[] loggerValues = cmdLine.getOptionValues(OPTION_LOG); |
| 241 | final int logNbr = loggerValues.length; |
| 242 | |
| 243 | // First we check if we have a logger type value |
| 244 | // Then, we check if the following value is not a logging type |
| 245 | // if not, we take this value which should be a path to a file |
| 246 | // Finally, we create a logger with the type and the option file |
| 247 | String logType = null; |
| 248 | for (int i = 0; i < logNbr; i++) { |
| 249 | final String value = loggerValues[i]; |
| 250 | |
| 251 | if (LoggerType.isLoggerTypeValue(value)) { |
| 252 | logType = value; |
| 253 | // System.out.println("\t\t-->found logger type: " + logType); |
| 254 | } |
| 255 | else { |
| 256 | final String loggingFile = value; |
| 257 | // System.out.println("\t\tfile found: " + loggingFile); |
| 258 | |
| 259 | if (logType != null) { |
| 260 | loggers.add(createLogger(logType, loggingFile)); |
| 261 | logType = null; |
| 262 | } |
| 263 | else { |
| 264 | // System.out.println("\t\t***skip value"); |
| 265 | } |
| 266 | } |
| 267 | } |
| 268 | |
| 269 | // The for remaining not registered logger |
| 270 | if (logType != null) { |
| 271 | // System.out.println("\t\t***Add logger found before exiting method"); |
| 272 | loggers.add(createLogger(logType, null)); |
| 273 | } |
| 274 | |
| 275 | } |
| 276 | |
| 277 | return loggers; |
| 278 | } |
| 279 | |
| 280 | /** |
| 281 | * Creates a Logger. |
| 282 | * @param logType the type of the logger to be created. |
| 283 | * @param loggingFile the logging file to be used by the Logger. May be <code>null</code> |
| 284 | * @return the created Logger. |
| 285 | * @throws UnSpecifiedFileException if the Logger to be created requires a logging file and |
| 286 | * loggingFile is <code>null</code> |
| 287 | * @throws IOException if an error occurs on file logger initialization. |
| 288 | */ |
| 289 | private Logger createLogger(final String logType, final String loggingFile) |
| 290 | throws UnSpecifiedFileException, IOException |
| 291 | { |
| 292 | // System.out.println("\t\tCreate logger"); |
| 293 | final LoggerCreator logCreator = new LoggerCreator(); |
| 294 | logCreator.setOutputFile(loggingFile); |
| 295 | logCreator.setLoggerType(logType); |
| 296 | return logCreator.createLogger(taskName); |
| 297 | } |
| 298 | |
| 299 | /** |
| 300 | * Returns a copy of the common options shared by all RbUtils command line utilities. |
| 301 | * @return the common options. |
| 302 | */ |
| 303 | protected static Options getCommonOptions() { |
| 304 | return clone(COMMON_OPTS); |
| 305 | } |
| 306 | |
| 307 | /** |
| 308 | * Clone an Options instance. |
| 309 | * @param options the instance to be cloned. |
| 310 | * @return the clone. |
| 311 | */ |
| 312 | protected static Options clone(final Options options) { |
| 313 | // Options is not Cloneable so we need to internally clone the instance |
| 314 | // TODO is there a problem here because we do not clone optionsGroup |
| 315 | final Options internalOptions = new Options(); |
| 316 | |
| 317 | final Collection listOpt = options.getOptions(); |
| 318 | for (final Iterator iter = listOpt.iterator(); iter.hasNext();) { |
| 319 | final Option opt = (Option) iter.next(); |
| 320 | // TODO Option do not clone all field (argName for instance) |
| 321 | internalOptions.addOption((Option) opt.clone()); |
| 322 | } |
| 323 | |
| 324 | return internalOptions; |
| 325 | } |
| 326 | |
| 327 | /** |
| 328 | * {@inheritDoc} |
| 329 | */ |
| 330 | public final void usage() { |
| 331 | usage(new PrintWriter(System.out)); |
| 332 | } |
| 333 | |
| 334 | /** |
| 335 | * {@inheritDoc} |
| 336 | */ |
| 337 | public final void usage(final PrintWriter pw) { |
| 338 | // TODO to be improved |
| 339 | // We should maybe extend the CLI library or better understand how it works... |
| 340 | final HelpFormatter hf = new HelpFormatter(); |
| 341 | // final PrintWriter pw = new PrintWriter(pw); |
| 342 | final String cmdLineSyntax = "java " + this.getClass().getName(); |
| 343 | final boolean autoUsage = false; |
| 344 | final String header = null; |
| 345 | final String footer = null; |
| 346 | |
| 347 | // hf.defaultSyntaxPrefix = "Parsed command line: "; |
| 348 | // final String header = hf.defaultNewLine + "Help"; |
| 349 | |
| 350 | // *** |
| 351 | // Act like printhelp with no usage |
| 352 | // *** |
| 353 | // printOptions(PrintWriter pw, int width, Options options, int leftPad, int descPad) |
| 354 | // hf.printOptions(pw, HelpFormatter.DEFAULT_WIDTH, options, HelpFormatter.DEFAULT_LEFT_PAD, |
| 355 | // HelpFormatter.DEFAULT_DESC_PAD); |
| 356 | |
| 357 | hf.printHelp(pw, HelpFormatter.DEFAULT_WIDTH, cmdLineSyntax, header, options, |
| 358 | HelpFormatter.DEFAULT_LEFT_PAD, HelpFormatter.DEFAULT_DESC_PAD, footer, |
| 359 | autoUsage); |
| 360 | |
| 361 | // hf.printUsage(pw, hf.defaultWidth, cmdLineSyntax, options); |
| 362 | pw.flush(); |
| 363 | } |
| 364 | |
| 365 | |
| 366 | /** |
| 367 | * {@inheritDoc} |
| 368 | */ |
| 369 | public final void parse(final Exception exception, final PrintWriter pw) { |
| 370 | final String msgHeader; |
| 371 | if (exception instanceof MissingOptionException) { |
| 372 | msgHeader = MSG_MISSING_REQUIRED_OPTION; |
| 373 | } |
| 374 | else if (exception instanceof MissingArgumentException) { |
| 375 | msgHeader = StringUtils.EMPTY; |
| 376 | } |
| 377 | else { |
| 378 | exception.printStackTrace(); |
| 379 | msgHeader = "unknown error: "; |
| 380 | } |
| 381 | pw.println("Initialization error - " + msgHeader + exception.getMessage()); |
| 382 | usage(pw); |
| 383 | } |
| 384 | } |