001: /********************************************************************************
002: * CruiseControl, a Continuous Integration Toolkit
003: * Copyright (c) 2001-2003, ThoughtWorks, Inc.
004: * 200 E. Randolph, 25th Floor
005: * Chicago, IL 60601 USA
006: * All rights reserved.
007: *
008: * Redistribution and use in source and binary forms, with or without
009: * modification, are permitted provided that the following conditions
010: * are met:
011: *
012: * + Redistributions of source code must retain the above copyright
013: * notice, this list of conditions and the following disclaimer.
014: *
015: * + Redistributions in binary form must reproduce the above
016: * copyright notice, this list of conditions and the following
017: * disclaimer in the documentation and/or other materials provided
018: * with the distribution.
019: *
020: * + Neither the name of ThoughtWorks, Inc., CruiseControl, nor the
021: * names of its contributors may be used to endorse or promote
022: * products derived from this software without specific prior
023: * written permission.
024: *
025: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
026: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
027: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
028: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
029: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
030: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
031: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
032: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
033: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
034: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
035: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
036: ********************************************************************************/package net.sourceforge.cruisecontrol;
037:
038: import java.io.File;
039: import java.io.IOException;
040: import java.util.Properties;
041:
042: import net.sourceforge.cruisecontrol.jmx.CruiseControlControllerAgent;
043: import net.sourceforge.cruisecontrol.launch.CruiseControlMain;
044: import net.sourceforge.cruisecontrol.launch.Launcher;
045: import net.sourceforge.cruisecontrol.report.BuildLoopMonitorRepository;
046: import net.sourceforge.cruisecontrol.report.BuildLoopPostingConfiguration;
047: import net.sourceforge.cruisecontrol.util.MainArgs;
048: import net.sourceforge.cruisecontrol.util.threadpool.ThreadQueueProperties;
049: import net.sourceforge.cruisecontrol.web.EmbeddedJettyServer;
050:
051: import org.apache.log4j.Level;
052: import org.apache.log4j.Logger;
053:
054: /**
055: * Command line entry point.
056: *
057: * @author alden almagro, ThoughtWorks, Inc. 2002
058: * @author <a href="mailto:jcyip@thoughtworks.com">Jason Yip</a>
059: */
060: public final class Main implements CruiseControlMain {
061:
062: private static final Logger LOG = Logger.getLogger(Main.class);
063:
064: /** the default name for the instance. */
065: private static final String DEFAULT_NAME = "";
066:
067: /**
068: * the default webapp directory.
069: */
070: private static final String DEFAULT_WEBAPP_PATH = "/webapps/cruisecontrol";
071:
072: /**
073: * the default dashboard (new webapp) directory.
074: */
075: private static final String DEFAULT_DASHBOARD_PATH = "/webapps/dashboard";
076:
077: /**
078: * the default port for the embedded Jetty.
079: */
080: private static final int DEFAULT_WEB_PORT = 8080;
081:
082: /**
083: * the default time interval used for http posting.
084: */
085: private static final int DEFAULT_INTERVAL = 5;
086:
087: private static final boolean DEFAULT_POSTING_ENABLED = true;
088:
089: /**
090: * Commandline entry point into the application.
091: *
092: * @deprecated Use the Launcher class instead
093: */
094: public static void main(String[] args) {
095: boolean normalExit = new Main().start(args);
096: if (!normalExit) {
097: System.exit(1);
098: }
099: }
100:
101: private CruiseControlController controller;
102:
103: private CruiseControlControllerAgent agent;
104:
105: /**
106: * Print the version, configure the project with serialized build info and/or arguments and start the project build
107: * process.
108: *
109: * @return true indicates normal return/exit.
110: */
111: public boolean start(String[] args) {
112: Properties versionProperties = getBuildVersionProperties();
113: printVersion(versionProperties);
114: if (shouldPrintUsage(args)) {
115: printUsage();
116: return false;
117: }
118: try {
119: checkDeprecatedArguments(args, LOG);
120: if (MainArgs.findIndex(args, "debug") != MainArgs.NOT_FOUND) {
121: Logger.getRootLogger().setLevel(Level.DEBUG);
122: }
123: controller = createController(args, versionProperties);
124: if (shouldStartJmxAgent(args)) {
125: startJmxAgent(args);
126: }
127: if (shouldStartEmbeddedServer(args)) {
128: startEmbeddedServer(args);
129: }
130: if (shouldPostDataToDashboard(args)) {
131: startPostingToDashboard(args);
132: }
133: parseCCName(args);
134: controller.resume();
135: } catch (CruiseControlException e) {
136: LOG.fatal(e.getMessage());
137: printUsage();
138: return false;
139: }
140: return true;
141: }
142:
143: private void startJmxAgent(String[] args) {
144: agent = new CruiseControlControllerAgent(controller,
145: parseJMXHttpPort(args), parseRmiPort(args),
146: parseUser(args), parsePassword(args),
147: parseXslPath(args));
148: agent.start();
149: }
150:
151: private CruiseControlController createController(String[] args,
152: Properties versionProperties) throws CruiseControlException {
153: CruiseControlController ccController = new CruiseControlController();
154: ccController.setVersionProperties(versionProperties);
155: File configFile = new File(parseConfigFileName(args,
156: CruiseControlController.DEFAULT_CONFIG_FILE_NAME));
157: try {
158: ccController.setConfigFile(configFile);
159: } catch (CruiseControlException e) {
160: LOG.error("error setting config file on controller", e);
161: throw e;
162: }
163: int maxNbThreads = ccController.getConfigManager()
164: .getCruiseControlConfig().getMaxNbThreads();
165: ThreadQueueProperties.setMaxThreadCount(maxNbThreads);
166: return ccController;
167: }
168:
169: /**
170: * Starts the embedded Jetty server on the port given by the command line argument -webport and loads the
171: * application from the path specified by the command line argument -webapppath. Uses default values if either
172: * argument are not specified.
173: *
174: * @param args command line arguments
175: */
176: void startEmbeddedServer(final String[] args)
177: throws CruiseControlException {
178: EmbeddedJettyServer embeddedJettyServer = new EmbeddedJettyServer(
179: parseWebPort(args),
180: parseWebappPath(args),
181: parseDashboardPath(args),
182: parseConfigFileName(
183: args,
184: CruiseControlController.DEFAULT_CONFIG_FILE_NAME),
185: parseJMXHttpPort(args), parseRmiPort(args));
186: embeddedJettyServer.start();
187:
188: }
189:
190: protected static void checkDeprecatedArguments(String[] args,
191: Logger logger) {
192: if (MainArgs.findIndex(args, "port") != MainArgs.NOT_FOUND) {
193: logger
194: .warn("WARNING: The port argument is deprecated. Use jmxport instead.");
195: }
196: }
197:
198: public static void printUsage() {
199: System.out.println("");
200: System.out.println("Usage:");
201: System.out.println("");
202: System.out.println("Starts a continuous integration loop");
203: System.out.println("");
204: System.out.println("cruisecontrol [options]");
205: System.out.println("");
206: System.out.println("Build loop options are:");
207: System.out.println("");
208: System.out
209: .println(" -configfile file configuration file; default config.xml");
210: System.out
211: .println(" -debug set logging level to DEBUG");
212: System.out
213: .println(" -? or -help print this usage message");
214: System.out.println("");
215: System.out.println("Options when using JMX");
216: System.out
217: .println(" Note: JMX server only started if -jmxport and/or -rmiport specified");
218: System.out
219: .println(" -jmxport [number] port of the JMX HttpAdapter; default 8000");
220: System.out
221: .println(" -rmiport [number] RMI port of the Controller; default 1099");
222: System.out
223: .println(" -user username username for HttpAdapter; default no login required");
224: System.out
225: .println(" -password pwd password for HttpAdapter; default no login required");
226: System.out
227: .println(" -xslpath directory location of jmx xsl files; default files in package");
228: System.out.println("");
229: System.out.println("Options when using embedded Jetty");
230: System.out
231: .println(" -webport [number] port for the Reporting website; default 8080, removing");
232: System.out
233: .println(" this propery will make cruisecontrol start without Jetty");
234: System.out
235: .println(" -webapppath directory location of the exploded WAR file for the legacy reporting");
236: System.out
237: .println(" application. default ./webapps/cruisecontrol");
238: System.out
239: .println(" -dashboard directory location of the exploded WAR file for the dashboard");
240: System.out
241: .println(" application. default ./webapps/dashboard");
242: System.out
243: .println(" -postenabled enabled switch of posting current build information to dashboard");
244: System.out.println(" default is true");
245: System.out
246: .println(" -dashboardurl url the url for dashboard (used for posting build information)");
247: System.out
248: .println(" default is http://localhost:8080/dashboard");
249: System.out
250: .println(" -postinterval interval how frequently build information will be posted to dashboard");
251: System.out
252: .println(" default is 5 (in second).");
253: System.out
254: .println(" -ccname name A logical name which will be displayed in the");
255: System.out
256: .println(" Reporting Application's status page.");
257: System.out.println("");
258: }
259:
260: /**
261: * Parse cc Name from arguments.
262: *
263: * @param args command line arguments.
264: * @return the name of this instance if specified on the command line, otherwise DEFAULT_NAME.
265: */
266: static String parseCCName(String[] args) {
267: String theCCName = MainArgs.parseArgument(args, "ccname",
268: DEFAULT_NAME, DEFAULT_NAME);
269: System.setProperty("ccname", theCCName);
270: return theCCName;
271: }
272:
273: static boolean shouldPostDataToDashboard(String[] args) {
274: return parseHttpPostingEnabled(args)
275: && BuildLoopMonitorRepository.getBuildLoopMonitor() == null;
276: }
277:
278: public void startPostingToDashboard(String[] args) {
279: String url = parseDashboardUrl(args);
280: long interval = parseHttpPostingInterval(args);
281: BuildLoopMonitorRepository.cancelExistingAndStartNewPosting(
282: controller, new BuildLoopPostingConfiguration(url,
283: interval));
284: }
285:
286: /**
287: * Parse webport from arguments.
288: *
289: * @param args command line arguments.
290: * @return the webport if specified on the command line, otherwise DEFAULT_WEB_PORT.
291: */
292: static int parseWebPort(String[] args) {
293: return MainArgs.parseInt(args, "webport", MainArgs.NOT_FOUND,
294: DEFAULT_WEB_PORT);
295: }
296:
297: /**
298: * Parse webapppath from arguments.
299: *
300: * @param args command line arguments.
301: * @return the webappdir if specified in the command line arguments, otherwise returns DEFAULT_WEBAPP_DIR.
302: */
303: String parseWebappPath(String[] args) {
304: String webappPath = MainArgs.parseArgument(args, "webapppath",
305: getDefaultWebAppPath(), getDefaultWebAppPath());
306: if (webappPath != null) {
307: validateWebAppPath(webappPath, "webapppath");
308: }
309: return webappPath;
310: }
311:
312: /**
313: * Creates the default webapppath by combining cchome and DEFAULT_WEBAPP_PATH
314: *
315: * @return the full default path
316: */
317: private String getDefaultWebAppPath() {
318: return System.getProperty(Launcher.CCHOME_PROPERTY, ".")
319: + DEFAULT_WEBAPP_PATH;
320: }
321:
322: /**
323: * Creates the default webapppath by combining cchome and DEFAULT_WEBAPP_PATH
324: *
325: * @return the full default path
326: */
327: private static String getDefaultDashboardPath() {
328: return System.getProperty(Launcher.CCHOME_PROPERTY, ".")
329: + DEFAULT_DASHBOARD_PATH;
330: }
331:
332: /**
333: * Parse dashboardpath (new webapp) from arguments.
334: *
335: * @param args command line arguments.
336: * @return the directory if specified in the command line arguments, otherwise returns DEFAULT_DASHBOARD_PATH.
337: */
338: static String parseDashboardPath(String[] args) {
339: String dashboardPath = MainArgs.parseArgument(args,
340: "dashboard", getDefaultDashboardPath(),
341: getDefaultDashboardPath());
342: if (dashboardPath != null) {
343: validateWebAppPath(dashboardPath, "dashboard");
344: }
345: return dashboardPath;
346: }
347:
348: private static void validateWebAppPath(String webappPath,
349: String path) {
350: File directory = new File(webappPath);
351: if (!directory.isDirectory()) {
352: throw new IllegalArgumentException(
353: "'"
354: + path
355: + "' argument must specify an existing directory but was "
356: + webappPath);
357: }
358: directory = new File(webappPath, "WEB-INF");
359: if (!directory.isDirectory()) {
360: throw new IllegalArgumentException("'" + path
361: + "' argument must point to an exploded web app. "
362: + "No WEB-INF directory exists for: " + webappPath);
363: }
364: }
365:
366: /**
367: * Parse configfile from arguments and override any existing configfile value from reading serialized Project info.
368: *
369: * @param configFileName existing configfile value read from serialized Project info
370: * @return final value of configFileName; never null
371: * @throws CruiseControlException if final configfile value is null
372: */
373: static String parseConfigFileName(String[] args,
374: String configFileName) throws CruiseControlException {
375: configFileName = MainArgs.parseArgument(args, "configfile",
376: configFileName, null);
377: if (configFileName == null) {
378: throw new CruiseControlException(
379: "'configfile' is a required argument to CruiseControl.");
380: }
381: return configFileName;
382: }
383:
384: static boolean shouldStartJmxAgent(String[] args) {
385: return MainArgs.argumentPresent(args, "jmxport")
386: || MainArgs.argumentPresent(args, "rmiport")
387: || MainArgs.argumentPresent(args, "port");
388: }
389:
390: /**
391: * If either -webport or -webapppath are specified on the command line, then the embedded Jetty server should be
392: * started, otherwise it should not.
393: *
394: * @param args command line arguments.
395: * @return true if the embedded Jetty server should be started, false if not.
396: */
397: static boolean shouldStartEmbeddedServer(String[] args) {
398: return MainArgs.argumentPresent(args, "webport")
399: || MainArgs.argumentPresent(args, "webapppath");
400: }
401:
402: /**
403: * Parse port number from arguments.
404: *
405: * @return port number
406: * @throws IllegalArgumentException if port argument is invalid
407: */
408: static int parseJMXHttpPort(String[] args) {
409: if (MainArgs.argumentPresent(args, "jmxport")
410: && MainArgs.argumentPresent(args, "port")) {
411: throw new IllegalArgumentException(
412: "'jmxport' and 'port' arguments are not valid together. Use"
413: + " 'jmxport' instead.");
414: } else if (MainArgs.argumentPresent(args, "jmxport")) {
415: return MainArgs.parseInt(args, "jmxport",
416: MainArgs.NOT_FOUND, 8000);
417: } else {
418: return MainArgs.parseInt(args, "port", MainArgs.NOT_FOUND,
419: 8000);
420: }
421: }
422:
423: static int parseRmiPort(String[] args) {
424: return MainArgs.parseInt(args, "rmiport", MainArgs.NOT_FOUND,
425: 1099);
426: }
427:
428: static String parseXslPath(String[] args) {
429: String xslpath = MainArgs.parseArgument(args, "xslpath", null,
430: null);
431: if (xslpath != null) {
432: File directory = new File(xslpath);
433: if (!directory.isDirectory()) {
434: throw new IllegalArgumentException(
435: "'xslpath' argument must specify an existing directory but was "
436: + xslpath);
437: }
438: }
439: return xslpath;
440: }
441:
442: /**
443: * Parse password from arguments and override any existing password value from reading serialized Project info.
444: *
445: * @return final value of password.
446: */
447: static String parsePassword(String[] args) {
448: return MainArgs.parseArgument(args, "password", null, null);
449: }
450:
451: /**
452: * Parse user from arguments and override any existing user value from reading serialized Project info.
453: *
454: * @return final value of user.
455: */
456: static String parseUser(String[] args) {
457: return MainArgs.parseArgument(args, "user", null, null);
458: }
459:
460: /**
461: * Retrieves the current version information, as indicated in the version.properties file.
462: */
463: private static Properties getBuildVersionProperties() {
464: Properties props = new Properties();
465: try {
466: props.load(Main.class
467: .getResourceAsStream("/version.properties"));
468: } catch (IOException e) {
469: LOG.error("Error reading version properties", e);
470: }
471: return props;
472: }
473:
474: /**
475: * Writes the current version information to the logging information stream.
476: */
477: private static void printVersion(Properties props) {
478: LOG.info("CruiseControl Version "
479: + props.getProperty("version") + " "
480: + props.getProperty("version.info"));
481: }
482:
483: static boolean shouldPrintUsage(String[] args) {
484: return MainArgs.findIndex(args, "?") != MainArgs.NOT_FOUND
485: || MainArgs.findIndex(args, "help") != MainArgs.NOT_FOUND;
486: }
487:
488: public void stop() {
489: controller.pause();
490: agent.stop();
491: }
492:
493: public static String parseDashboardUrl(String[] args) {
494: int webport = parseWebPort(args);
495: if (webport == MainArgs.NOT_FOUND) {
496: webport = 8080;
497: }
498: return MainArgs
499: .parseArgument(args, "dashboardurl",
500: defaultDashboardUrl(webport),
501: defaultDashboardUrl(8080));
502: }
503:
504: private static String defaultDashboardUrl(int port) {
505: return "http://localhost:" + port + "/dashboard";
506: }
507:
508: public static long parseHttpPostingInterval(String[] args) {
509: return MainArgs.parseInt(args, "postinterval",
510: DEFAULT_INTERVAL, DEFAULT_INTERVAL);
511: }
512:
513: public static boolean parseHttpPostingEnabled(String[] args) {
514: return MainArgs.parseBoolean(args, "postenabled",
515: DEFAULT_POSTING_ENABLED, DEFAULT_POSTING_ENABLED);
516: }
517: }
|