001/*
002 * #%L
003 * Netarchivesuite - deploy
004 * %%
005 * Copyright (C) 2005 - 2014 The Royal Danish Library, the Danish State and University Library,
006 *             the National Library of France and the Austrian National Library.
007 * %%
008 * This program is free software: you can redistribute it and/or modify
009 * it under the terms of the GNU Lesser General Public License as
010 * published by the Free Software Foundation, either version 2.1 of the
011 * License, or (at your option) any later version.
012 * 
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Lesser Public License for more details.
017 * 
018 * You should have received a copy of the GNU General Lesser Public
019 * License along with this program.  If not, see
020 * <http://www.gnu.org/licenses/lgpl-2.1.html>.
021 * #L%
022 */
023package dk.netarkivet.deploy;
024
025import java.io.File;
026import java.io.IOException;
027import java.nio.charset.Charset;
028
029
030import org.apache.commons.cli.CommandLine;
031import org.apache.commons.cli.CommandLineParser;
032import org.apache.commons.cli.Option;
033import org.apache.commons.cli.Options;
034import org.apache.commons.cli.ParseException;
035import org.apache.commons.cli.PosixParser;
036
037import dk.netarkivet.common.utils.Settings;
038
039// JDK8-import
040//import java.util.Optional;
041
042
043/**
044 * The application that is run to generate install and start/stop scripts for all physical locations, machines and
045 * applications.
046 * <p>
047 * The actual deployment has to be done from an Linux/Unix machine, and this application should therefore not be run on
048 * Windows.
049 */
050public final class DeployApplication {
051
052    static {
053        Settings.addDefaultClasspathSettings(Constants.BUILD_COMPLETE_SETTINGS_FILE_PATH);
054    }
055
056    /** The configuration for this deploy. */
057    private static DeployConfiguration deployConfig;
058    /** Argument parameter. */
059    private static ArgumentParameters ap = new ArgumentParameters();
060    /** The deploy-config file. */
061    private static File deployConfigFile;
062    /** The NetarchiveSuite file. */
063    private static File netarchiveSuiteFile;
064    /** The security policy file. */
065    private static File secPolicyFile;
066    /** SLF4J xml configuration file. */
067    private static File slf4jConfigFile;
068    /** The database file. */
069    private static File dbFile;
070    /** The arguments for resetting tempDir. */
071    private static boolean resetDirectory;
072    /** The archive database file. */
073    private static File arcDbFile;
074    /** The folder with the external libraries to be deployed. */
075    private static File externalJarFolder;
076    
077    //private static Optional<File> defaultBundlerZip;
078    private static File defaultBundlerZip;
079    /**
080     * Private constructor to disallow instantiation of this class.
081     */
082    private DeployApplication() {
083    }
084
085    /**
086     * Run deploy.
087     *
088     * @param args The Command-line arguments in no particular order:
089     *
090     * -C The deploy configuration file (ends with .xml). -Z The NetarchiveSuite file to be unpacked (ends with .zip).
091     * -S The security policy file (ends with .policy). -L The logging property file (ends with .prop). -O [OPTIONAL]
092     * The output directory -D [OPTIONAL] The harvest definition database -T [OPTIONAL] The test arguments
093     * (httpportoffset, port, environmentName, mailReceiver) -R [OPTIONAL] For resetting the tempDir (takes arguments
094     * 'y' or 'yes') -E [OPTIONAL] Evaluating the deployConfig file (arguments: 'y' or 'yes') -A [OPTIONAL] For archive
095     * database. -J [OPTIONAL] For deploying with external jar files. Must be the total path to the directory containing
096     * jar-files. These external files will be placed on every machine, and they have to manually be put into the
097     * classpath, where they should be used.
098     */
099    public static void main(String[] args) {
100        try {
101            // Make sure the arguments can be parsed.
102            if (!ap.parseParameters(args)) {
103                System.err.print(Constants.MSG_ERROR_PARSE_ARGUMENTS);
104                System.out.println(ap.listArguments());
105                System.exit(1);
106            }
107
108            // Check arguments
109            if (ap.getCommandLine().getOptions().length < Constants.ARGUMENTS_REQUIRED) {
110                System.err.print(Constants.MSG_ERROR_NOT_ENOUGH_ARGUMENTS);
111                System.out.println();
112                System.out.println("Use DeployApplication with following arguments:");
113                System.out.println(ap.listArguments());
114                System.out.println("outputdir defaults to ./environmentName (set in config file)");
115                System.exit(1);
116            }
117            // test if more arguments than options is given
118            if (args.length > ap.getOptions().getOptions().size()) {
119                System.err.print(Constants.MSG_ERROR_TOO_MANY_ARGUMENTS);
120                System.out.println();
121                System.out.println("Maximum " + ap.getOptions().getOptions().size() + "arguments.");
122                System.exit(1);
123            }
124
125            // Retrieving the configuration filename
126            String deployConfigFileName = ap.getCommandLine().getOptionValue(Constants.ARG_CONFIG_FILE);
127            // Retrieving the NetarchiveSuite filename
128            String netarchiveSuiteFileName = ap.getCommandLine().getOptionValue(Constants.ARG_NETARCHIVE_SUITE_FILE);
129            // Retrieving the security policy filename
130            String secPolicyFileName = ap.getCommandLine().getOptionValue(Constants.ARG_SECURITY_FILE);
131            // Retrieving the SLF4J xml filename
132            String slf4jConfigFileName = ap.getCommandLine().getOptionValue(Constants.ARG_SLF4J_CONFIG_FILE);
133            // Retrieving the output directory name
134            String outputDir = ap.getCommandLine().getOptionValue(Constants.ARG_OUTPUT_DIRECTORY);
135            // Retrieving the database filename
136            String databaseFileName = ap.getCommandLine().getOptionValue(Constants.ARG_DATABASE_FILE);
137            // Retrieving the test arguments
138            String testArguments = ap.getCommandLine().getOptionValue(Constants.ARG_TEST);
139            // Retrieving the reset argument
140            String resetArgument = ap.getCommandLine().getOptionValue(Constants.ARG_RESET);
141            // Retrieving the evaluate argument
142            String evaluateArgument = ap.getCommandLine().getOptionValue(Constants.ARG_EVALUATE);
143            // Retrieve the archive database filename.
144            String arcDbFileName = ap.getCommandLine().getOptionValue(Constants.ARG_ARC_DB);
145            // Retrieves the jar-folder name.
146            String jarFolderName = ap.getCommandLine().getOptionValue(Constants.ARG_JAR_FOLDER);
147
148            // Retrieves the source encoding.
149            // If not specified get system default
150            String sourceEncoding = ap.getCommandLine().getOptionValue(Constants.ARG_SOURCE_ENCODING);
151            String msgTail = "";
152            if (sourceEncoding == null || sourceEncoding.isEmpty()) {
153                sourceEncoding = Charset.defaultCharset().name();
154                msgTail = " (defaulted)";
155            }
156            System.out.println("Will read source files using encoding '" + sourceEncoding + "'" + msgTail);
157
158            // check deployConfigFileName and retrieve the corresponding file
159            initConfigFile(deployConfigFileName);
160
161            // check netarchiveSuiteFileName and retrieve the corresponding file
162            initNetarchiveSuiteFile(netarchiveSuiteFileName);
163
164            // check secPolicyFileName and retrieve the corresponding file
165            initSecPolicyFile(secPolicyFileName);
166
167            initSLF4JXmlFile(slf4jConfigFileName);
168
169            // check database
170            initDatabase(databaseFileName);
171
172            // check and apply the test arguments
173            initTestArguments(testArguments);
174
175            // check reset arguments.
176            initReset(resetArgument);
177
178            // evaluates the config file
179            initEvaluate(evaluateArgument, sourceEncoding);
180
181            // check the archive database
182            initArchiveDatabase(arcDbFileName);
183
184            // check the external jar-files library folder.
185            initJarFolder(jarFolderName);
186
187            //initBundlerZip(Optional.ofNullable(
188            //        ap.getCommandLine().getOptionValue(Constants.ARG_DEFAULT_BUNDLER_ZIP)));
189            initBundlerZip(ap.getCommandLine().getOptionValue(Constants.ARG_DEFAULT_BUNDLER_ZIP));
190
191            // Make the configuration based on the input data
192            deployConfig = new DeployConfiguration(deployConfigFile, netarchiveSuiteFile, secPolicyFile,
193                    slf4jConfigFile, outputDir, dbFile, arcDbFile, resetDirectory, externalJarFolder, sourceEncoding,
194                    defaultBundlerZip);
195
196            // Write the scripts, directories and everything
197            deployConfig.write();
198        } catch (SecurityException e) {
199            // This problem should only occur in tests -> thus not err message.
200            System.out.println("SECURITY ERROR: ");
201            e.printStackTrace();
202        } catch (Throwable e) {
203            System.err.println("DEPLOY APPLICATION ERROR: ");
204            e.printStackTrace();
205        }
206    }
207
208    /**
209     * Checks the configuration file argument and retrieves the file.
210     *
211     * @param deployConfigFileName The configuration file argument.
212     */
213    private static void initConfigFile(String deployConfigFileName) {
214        // check whether deploy-config file name is given as argument
215        if (deployConfigFileName == null) {
216            System.err.print(Constants.MSG_ERROR_NO_CONFIG_FILE_ARG);
217            System.out.println();
218            System.exit(1);
219        }
220        // check whether deploy-config file has correct extensions
221        if (!deployConfigFileName.endsWith(Constants.EXTENSION_XML_FILES)) {
222            System.err.print(Constants.MSG_ERROR_CONFIG_EXTENSION);
223            System.out.println();
224            System.exit(1);
225        }
226        // get the file
227        deployConfigFile = new File(deployConfigFileName);
228        // check whether the deploy-config file exists.
229        if (!deployConfigFile.exists()) {
230            System.err.print(Constants.MSG_ERROR_NO_CONFIG_FILE_FOUND);
231            System.out.println();
232            System.exit(1);
233        }
234    }
235
236    /**
237     * Checks the NetarchiveSuite file argument and retrieves the file.
238     *
239     * @param netarchiveSuiteFileName The NetarchiveSuite argument.
240     */
241    private static void initNetarchiveSuiteFile(String netarchiveSuiteFileName) {
242        // check whether NetarchiveSuite file name is given as argument
243        if (netarchiveSuiteFileName == null) {
244            System.err.print(Constants.MSG_ERROR_NO_NETARCHIVESUITE_FILE_ARG);
245            System.out.println();
246            System.exit(1);
247        }
248        // check whether the NetarchiveSuite file has correct extensions
249        if (!netarchiveSuiteFileName.endsWith(Constants.EXTENSION_ZIP_FILES)) {
250            System.err.print(Constants.MSG_ERROR_NETARCHIVESUITE_EXTENSION);
251            System.out.println();
252            System.exit(1);
253        }
254        // get the file
255        netarchiveSuiteFile = new File(netarchiveSuiteFileName);
256        // check whether the NetarchiveSuite file exists.
257        if (!netarchiveSuiteFile.isFile()) {
258            System.err.print(Constants.MSG_ERROR_NO_NETARCHIVESUITE_FILE_FOUND);
259            System.out.println();
260            System.out.println("Couldn't find file: " + netarchiveSuiteFile.getAbsolutePath());
261            System.exit(1);
262        }
263    }
264
265    /**
266     * Checks the security policy file argument and retrieves the file.
267     *
268     * @param secPolicyFileName The security policy argument.
269     */
270    private static void initSecPolicyFile(String secPolicyFileName) {
271        // check whether security policy file name is given as argument
272        if (secPolicyFileName == null) {
273            System.err.print(Constants.MSG_ERROR_NO_SECURITY_FILE_ARG);
274            System.out.println();
275            System.exit(1);
276        }
277        // check whether security policy file has correct extensions
278        if (!secPolicyFileName.endsWith(Constants.EXTENSION_POLICY_FILES)) {
279            System.err.print(Constants.MSG_ERROR_SECURITY_EXTENSION);
280            System.out.println();
281            System.exit(1);
282        }
283        // get the file
284        secPolicyFile = new File(secPolicyFileName);
285        // check whether the security policy file exists.
286        if (!secPolicyFile.exists()) {
287            System.err.print(Constants.MSG_ERROR_NO_SECURITY_FILE_FOUND);
288            System.out.println();
289            System.out.println("Couldn't find file: " + secPolicyFile.getAbsolutePath());
290            System.exit(1);
291        }
292    }
293
294    /**
295     * Checks the SLF4J config file argument and retrieves the file.
296     *
297     * @param slf4jXmlFileName The SLF4J config argument.
298     */
299    private static void initSLF4JXmlFile(String slf4jXmlFileName) {
300        // check whether SLF4J config file name is given as argument
301        if (slf4jXmlFileName == null) {
302            System.err.print(Constants.MSG_ERROR_NO_SLF4J_CONFIG_FILE_ARG);
303            System.out.println();
304            System.exit(1);
305        }
306        // check whether the SLF4J xml file has correct extensions
307        if (!slf4jXmlFileName.endsWith(Constants.EXTENSION_XML_FILES)) {
308            System.err.print(Constants.MSG_ERROR_SLF4J_CONFIG_EXTENSION);
309            System.out.println();
310            System.exit(1);
311        }
312        // get the file
313        slf4jConfigFile = new File(slf4jXmlFileName);
314        // check whether the SLF4J xml file exists.
315        if (!slf4jConfigFile.exists()) {
316            System.err.print(Constants.MSG_ERROR_NO_SLF4J_CONFIG_FILE_FOUND);
317            System.out.println();
318            System.out.println("Couldn't find file: " + slf4jConfigFile.getAbsolutePath());
319            System.exit(1);
320        }
321    }
322
323    /**
324     * Checks the database argument (if any) for extension and existence.
325     *
326     * @param databaseFileName The name of the database file.
327     */
328    private static void initDatabase(String databaseFileName) {
329        dbFile = null;
330        // check the extension on the database, if it is given as argument
331        if (databaseFileName != null) {
332            if (!databaseFileName.endsWith(Constants.EXTENSION_JAR_FILES)
333                    && !databaseFileName.endsWith(Constants.EXTENSION_ZIP_FILES)) {
334                System.err.print(Constants.MSG_ERROR_DATABASE_EXTENSION);
335                System.out.println();
336                System.exit(1);
337            }
338
339            // get the file
340            dbFile = new File(databaseFileName);
341            // check whether the database file exists.
342            if (!dbFile.isFile()) {
343                System.err.print(Constants.MSG_ERROR_NO_DATABASE_FILE_FOUND);
344                System.out.println();
345                System.out.println("Couldn't find file: " + dbFile.getAbsolutePath());
346                System.exit(1);
347            }
348        }
349    }
350
351    /**
352     * Checks the arguments for resetting the directory. Only the arguments 'y' or 'yes' resets the database directory.
353     * Default is 'no'.
354     * <p>
355     * If another argument than 'y', 'yes', 'n' or 'no' is given, an warning is given.
356     *
357     * @param resetArgument The argument for resetting given.
358     */
359    private static void initReset(String resetArgument) {
360        if (resetArgument != null) {
361            if (resetArgument.equalsIgnoreCase(Constants.YES_SHORT)
362                    || resetArgument.equalsIgnoreCase(Constants.YES_LONG)) {
363                // if positive argument, then set to true.
364                resetDirectory = true;
365            } else if (resetArgument.equalsIgnoreCase(Constants.NO_SHORT)
366                    || resetArgument.equalsIgnoreCase(Constants.NO_LONG)) {
367                // if negative argument, then set to false.
368                resetDirectory = false;
369            } else {
370                // if wrong argument, notify and set to false.
371                System.err.println(Constants.MSG_ERROR_RESET_ARGUMENT);
372                resetDirectory = false;
373            }
374        } else {
375            // if no arguments, then
376            resetDirectory = false;
377        }
378    }
379
380    /**
381     * Checks the arguments for evaluating the config file. Only the arguments 'y' or 'yes' is accepted for evaluation.
382     * Anything else (including argument set to null) does not evaluate the deployConfigFile.
383     *
384     * @param evaluateArgument The argument for evaluation.
385     * @param encoding the encoding to use to read from the input file
386     */
387    public static void initEvaluate(String evaluateArgument, String encoding) {
388        // check if argument is given and it is acknowledgement ('y' or 'yes')
389        if ((evaluateArgument != null)
390                && (!evaluateArgument.isEmpty())
391                && (evaluateArgument.equalsIgnoreCase(Constants.YES_SHORT) || evaluateArgument
392                        .equalsIgnoreCase(Constants.YES_LONG))) {
393            // if yes, then evaluate config file
394            EvaluateConfigFile evf = new EvaluateConfigFile(deployConfigFile, encoding);
395            evf.evaluate();
396        }
397    }
398
399    /**
400     * Checks the argument for the archive database.
401     *
402     * @param arcDbFileName The path to the archive database.
403     */
404    public static void initArchiveDatabase(String arcDbFileName) {
405        arcDbFile = null;
406        // check the extension on the database, if it is given as argument
407        if (arcDbFileName != null) {
408            if (!arcDbFileName.endsWith(Constants.EXTENSION_JAR_FILES)
409                    && !arcDbFileName.endsWith(Constants.EXTENSION_ZIP_FILES)) {
410                System.err.print(Constants.MSG_ERROR_BPDB_EXTENSION);
411                System.out.println();
412                System.exit(1);
413            }
414
415            // get the file
416            arcDbFile = new File(arcDbFileName);
417            // check whether the database file exists.
418            if (!arcDbFile.isFile()) {
419                System.err.print(Constants.MSG_ERROR_NO_BPDB_FILE_FOUND);
420                System.out.println();
421                System.out.println("Couldn't find file: " + arcDbFile.getAbsolutePath());
422                System.exit(1);
423            }
424        }
425    }
426
427    /**
428     * Checks the argument for the external jar-folder.
429     *
430     * @param folderName The path to the folder. Global path.
431     */
432    public static void initJarFolder(String folderName) {
433        externalJarFolder = null;
434        if (folderName != null && !folderName.isEmpty()) {
435            externalJarFolder = new File(folderName);
436
437            if (!externalJarFolder.isDirectory()) {
438                System.err.print(Constants.MSG_ERROR_NO_JAR_FOLDER);
439                System.out.println();
440                System.out.println("Couldn't find directory: " + externalJarFolder.getAbsolutePath());
441                System.exit(1);
442            }
443        }
444    }
445
446    /**
447     * Checks if the default bundler zip file exists if defined.
448     *
449     * @param defaultBundlerZipName The path to the default bundler zip file to use.
450     */
451    public static void initBundlerZip(String defaultBundlerZipName) {
452        if (defaultBundlerZipName != null) {
453            defaultBundlerZip = new File(defaultBundlerZipName);
454            if (!defaultBundlerZip.exists()) {
455                System.err.print(Constants.MSG_ERROR_NO_BUNDLER_ZIP_FILE);
456                System.out.println();
457                System.out.println("Couldn't find the default bundler file: " + defaultBundlerZip.getAbsolutePath());
458                System.exit(1);
459            }
460        }
461    }
462
463    /**
464     * Applies the test arguments.
465     * <p>
466     * If the test arguments are given correctly, the configuration file is loaded and changed appropriately, then
467     * written to a test configuration file.
468     * <p>
469     * The new test configuration file has the same name as the original configuration file, except ".xml" is replaced
470     * by "_text.xml". Thus "path/config.xml" -> "path/config_test.xml".
471     *
472     * @param testArguments The test arguments.
473     */
474    private static void initTestArguments(String testArguments) {
475        // test if any test arguments (if none, don't apply, just stop).
476        if (testArguments == null || testArguments.isEmpty()) {
477            return;
478        }
479
480        String[] changes = testArguments.split(Constants.REGEX_COMMA_CHARACTER);
481        if (changes.length != Constants.TEST_ARGUMENTS_REQUIRED) {
482            System.err.print(Constants.MSG_ERROR_TEST_ARGUMENTS);
483            System.out.println();
484            System.out.println(changes.length + " arguments was given and " + Constants.TEST_ARGUMENTS_REQUIRED
485                    + " was expected.");
486            System.out.println("Received: " + testArguments);
487            System.exit(1);
488        }
489
490        try {
491            CreateTestInstance cti = new CreateTestInstance(deployConfigFile);
492
493            // apply the arguments
494            cti.applyTestArguments(changes[Constants.TEST_ARGUMENT_OFFSET_INDEX],
495                    changes[Constants.TEST_ARGUMENT_HTTP_INDEX],
496                    changes[Constants.TEST_ARGUMENT_ENVIRONMENT_NAME_INDEX],
497                    changes[Constants.TEST_ARGUMENT_MAIL_INDEX]);
498
499            // replace ".xml" with "_test.xml"
500            String tmp = deployConfigFile.getPath();
501            // split this into two ("path/config.xml" = {"path/config", ".xml"})
502            String[] configFile = tmp.split(Constants.REGEX_DOT_CHARACTER);
503            // take the first part and add test ending
504            // ("path/config" + "_test.xml" = "path/config_test.xml")
505            String nameOfNewConfig = configFile[0] + Constants.TEST_CONFIG_FILE_REPLACE_ENDING;
506
507            // create and use new config file.
508            cti.createConfigurationFile(nameOfNewConfig);
509            deployConfigFile = new File(nameOfNewConfig);
510        } catch (IOException e) {
511            System.out.println("Error in test arguments: " + e);
512            System.exit(1);
513        }
514    }
515
516    /**
517     * Handles the incoming arguments.
518     */
519    private static class ArgumentParameters {
520        /** Options object for parameters. */
521        private Options options = new Options();
522        /** Parser for parsing the command line arguments. */
523        private CommandLineParser parser = new PosixParser();
524        /** The command line. */
525        private CommandLine cmd;
526        /** Whether the options has an argument. */
527        private static final boolean HAS_ARG = true;
528
529        /**
530         * Initialise options by setting legal parameters for batch jobs.
531         */
532        ArgumentParameters() {
533            options.addOption(Constants.ARG_CONFIG_FILE, HAS_ARG, "Config file.");
534            options.addOption(Constants.ARG_NETARCHIVE_SUITE_FILE, HAS_ARG, "The NetarchiveSuite package file.");
535            options.addOption(Constants.ARG_SECURITY_FILE, HAS_ARG, "Security property file.");
536            options.addOption(Constants.ARG_SLF4J_CONFIG_FILE, HAS_ARG, "SLF4J config file.");
537            options.addOption(Constants.ARG_OUTPUT_DIRECTORY, HAS_ARG, "[OPTIONAL] output directory.");
538            options.addOption(Constants.ARG_DATABASE_FILE, HAS_ARG, "[OPTIONAL] Database file.");
539            options.addOption(Constants.ARG_TEST, HAS_ARG, "[OPTIONAL] Tests arguments (offset for http port,"
540                    + " http port, environment name, mail receiver).");
541            options.addOption(Constants.ARG_RESET, HAS_ARG, "[OPTIONAL] Reset temp directory ('y' or 'yes'"
542                    + "means reset, anything else means do not reset."
543                    + " Different from 'y', 'yes', 'n' or 'no' gives" + " an error message.");
544            options.addOption(Constants.ARG_EVALUATE, HAS_ARG, "[OPTIONAL] Evaluate the config file.");
545            options.addOption(Constants.ARG_ARC_DB, HAS_ARG, "[OPTIONAL] The archive database file");
546            options.addOption(Constants.ARG_JAR_FOLDER, HAS_ARG, "[OPTIONAL] Installing the external jar library "
547                    + "files within the given folder.");
548            options.addOption(Constants.ARG_SOURCE_ENCODING, HAS_ARG, "[OPTIONAL] Encoding to use for source files.");
549            options.addOption(Constants.ARG_DEFAULT_BUNDLER_ZIP, HAS_ARG, "[OPTIONAL] The bundled harvester to use. "
550                    + "If not provided here the bundler needs to be provided in the settings for each (H3) harvester");
551        }
552
553        /**
554         * Parsing the input arguments.
555         *
556         * @param args The input arguments.
557         * @return Whether it parsed correctly or not.
558         */
559        Boolean parseParameters(String[] args) {
560            try {
561                // parse the command line arguments
562                cmd = parser.parse(options, args);
563            } catch (ParseException exp) {
564                System.out.println("Parsing error: " + exp);
565                return false;
566            }
567            return true;
568        }
569
570        /**
571         * Get the list of possible arguments with their description.
572         *
573         * @return The list describing the possible arguments.
574         */
575        String listArguments() {
576            StringBuilder res = new StringBuilder();
577            res.append(Constants.NEWLINE);
578            res.append(Constants.INIT_ARGUMENTS_LIST);
579            // add options
580            for (Object o : options.getOptions()) {
581                Option op = (Option) o;
582                res.append(Constants.NEWLINE);
583                res.append(Constants.DASH);
584                res.append(op.getOpt());
585                res.append(Constants.SPACE);
586                res.append(op.getDescription());
587            }
588            return res.toString();
589        }
590
591        /**
592         * For retrieving the options.
593         *
594         * @return The options.
595         */
596        public Options getOptions() {
597            return options;
598        }
599
600        /**
601         * For retrieving the commandLine.
602         *
603         * @return The cmd.
604         */
605        public CommandLine getCommandLine() {
606            return cmd;
607        }
608    }
609
610}