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 | } |