private static void deleteContents(File file) throws IOException {
   if (file.isDirectory()) {
     File[] files = file.listFiles();
     if (files != null) { // be defensive
       for (int i = 0; i < files.length; i++) deleteContents(files[i]);
     }
   }
   file.delete();
 }
  private static void _main(String[] args) throws Exception {
    File extractedFilesFolder = null;
    for (int i = 0; i < args.length; i++) {
      if (args[i].startsWith("--extractedFilesFolder=")) {
        extractedFilesFolder = new File(args[i].substring("--extractedFilesFolder=".length()));
        if (!extractedFilesFolder.isDirectory()) {
          System.err.println("The extractedFilesFolder value is not a directory. Ignoring.");
          extractedFilesFolder = null;
        }
      }
    }

    // if we need to daemonize, do it first
    for (int i = 0; i < args.length; i++) {
      if (args[i].startsWith("--daemon")) {
        Map revisions = parseDependencyVersions();

        // load the daemonization code
        ClassLoader cl =
            new URLClassLoader(
                new URL[] {
                  extractFromJar(
                          "WEB-INF/lib/jna-"
                              + getVersion(revisions, "net.java.dev.jna", "jna")
                              + ".jar",
                          "jna",
                          "jar",
                          extractedFilesFolder)
                      .toURI()
                      .toURL(),
                  extractFromJar(
                          "WEB-INF/lib/akuma-"
                              + getVersion(revisions, "org.kohsuke", "akuma")
                              + ".jar",
                          "akuma",
                          "jar",
                          extractedFilesFolder)
                      .toURI()
                      .toURL(),
                });
        Class $daemon = cl.loadClass("com.sun.akuma.Daemon");
        Object daemon = $daemon.newInstance();

        // tell the user that we'll be starting as a daemon.
        Method isDaemonized = $daemon.getMethod("isDaemonized", new Class[] {});
        if (!((Boolean) isDaemonized.invoke(daemon, new Object[0])).booleanValue()) {
          System.out.println("Forking into background to run as a daemon.");
          if (!hasLogOption(args)) System.out.println("Use --logfile to redirect output to a file");
        }

        Method m = $daemon.getMethod("all", new Class[] {boolean.class});
        m.invoke(daemon, new Object[] {Boolean.TRUE});
      }
    }

    // if the output should be redirect to a file, do it now
    for (int i = 0; i < args.length; i++) {
      if (args[i].startsWith("--logfile=")) {
        LogFileOutputStream los =
            new LogFileOutputStream(new File(args[i].substring("--logfile=".length())));
        PrintStream ps = new PrintStream(los);
        System.setOut(ps);
        System.setErr(ps);
        // don't let winstone see this
        List _args = new ArrayList(Arrays.asList(args));
        _args.remove(i);
        args = (String[]) _args.toArray(new String[_args.size()]);
        break;
      }
    }

    // this is so that JFreeChart can work nicely even if we are launched as a daemon
    System.setProperty("java.awt.headless", "true");

    // tell Jenkins that Winstone doesn't support chunked encoding.
    // this is no longer neede for Winstone 2.x
    if (System.getProperty("hudson.diyChunking") == null)
      System.setProperty("hudson.diyChunking", "true");

    File me = whoAmI(extractedFilesFolder);
    System.out.println("Running from: " + me);
    System.setProperty(
        "executable-war",
        me.getAbsolutePath()); // remember the location so that we can access it from within webapp

    // figure out the arguments
    List arguments = new ArrayList(Arrays.asList(args));
    trimOffOurOptions(arguments);
    arguments.add(0, "--warfile=" + me.getAbsolutePath());
    if (!hasWebRoot(arguments)) {
      // defaults to ~/.jenkins/war since many users reported that cron job attempts to clean up
      // the contents in the temporary directory.
      final FileAndDescription describedHomeDir = getHomeDir();
      System.out.println("webroot: " + describedHomeDir.description);
      arguments.add("--webroot=" + new File(describedHomeDir.file, "war"));
    }

    if (arguments.contains("--version")) {
      System.out.println(getVersion("?"));
      return;
    }

    // put winstone jar in a file system so that we can load jars from there
    File tmpJar = extractFromJar("winstone.jar", "winstone", ".jar", extractedFilesFolder);

    // clean up any previously extracted copy, since
    // winstone doesn't do so and that causes problems when newer version of Jenkins
    // is deployed.
    File tempFile = File.createTempFile("dummy", "dummy");
    deleteContents(new File(tempFile.getParent(), "winstone/" + me.getName()));
    tempFile.delete();

    // locate the Winstone launcher
    ClassLoader cl = new URLClassLoader(new URL[] {tmpJar.toURI().toURL()});
    Class launcher = cl.loadClass("winstone.Launcher");
    Method mainMethod = launcher.getMethod("main", new Class[] {String[].class});

    // override the usage screen
    Field usage = launcher.getField("USAGE");
    usage.set(
        null,
        "Jenkins Continuous Integration Engine "
            + getVersion("")
            + "\n"
            + "Usage: java -jar jenkins.war [--option=value] [--option=value]\n"
            + "\n"
            + "Options:\n"
            + "   --extractedFilesFolder   = folder where extracted files are to be located. Default is the temp folder\n"
            + "   --daemon                 = fork into background and run as daemon (Unix only)\n"
            + "   --config                 = load configuration properties from here. Default is ./winstone.properties\n"
            + "   --prefix                 = add this prefix to all URLs (eg http://localhost:8080/prefix/resource). Default is none\n"
            + "   --commonLibFolder        = folder for additional jar files. Default is ./lib\n"
            + "   \n"
            + "   --logfile                = redirect log messages to this file\n"
            + "   --logThrowingLineNo      = show the line no that logged the message (slow). Default is false\n"
            + "   --logThrowingThread      = show the thread that logged the message. Default is false\n"
            + "   --debug                  = set the level of debug msgs (1-9). Default is 5 (INFO level)\n"
            + "\n"
            +
            // from
            // jenkinsci/winstone/src/java/winstone/LocalStrings.properties#Launcher.UsageInstructions
            "   --httpPort               = set the http listening port. -1 to disable, Default is 8080\n"
            + "   --httpListenAddress      = set the http listening address. Default is all interfaces\n"
            + "   --httpDoHostnameLookups  = enable host name lookups on incoming http connections (true/false). Default is false\n"
            + "   --httpKeepAliveTimeout   = how long idle HTTP keep-alive connections are kept around (in ms; default 5000)?\n"
            + "   --httpsPort              = set the https listening port. -1 to disable, Default is disabled\n"
            + "                              if neither --httpsCertificate nor --httpsKeyStore are specified,\n"
            + "                              https is run with one-time self-signed certificate.\n"
            + "   --httpsListenAddress     = set the https listening address. Default is all interfaces\n"
            + "   --httpsDoHostnameLookups = enable host name lookups on incoming https connections (true/false). Default is false\n"
            + "   --httpsKeepAliveTimeout   = how long idle HTTPS keep-alive connections are kept around (in ms; default 5000)?\n"
            + "   --httpsKeyStore          = the location of the SSL KeyStore file.\n"
            + "   --httpsKeyStorePassword  = the password for the SSL KeyStore file. Default is null\n"
            + "   --httpsCertificate       = the location of the PEM-encoded SSL certificate file.\n"
            + "                              (the one that starts with '-----BEGIN CERTIFICATE-----')\n"
            + "                              must be used with --httpsPrivateKey.\n"
            + "   --httpsPrivateKey        = the location of the PEM-encoded SSL private key.\n"
            + "                              (the one that starts with '-----BEGIN RSA PRIVATE KEY-----')\n"
            + "   --httpsKeyManagerType    = the SSL KeyManagerFactory type (eg SunX509, IbmX509). Default is SunX509\n"
            + "   --spdy                   = Enable SPDY. See http://wiki.eclipse.org/Jetty/Feature/NPN\n"
            + "   --ajp13Port              = set the ajp13 listening port. -1 to disable, Default is disabled\n"
            + "   --ajp13ListenAddress     = set the ajp13 listening address. Default is all interfaces\n"
            + "   --controlPort            = set the shutdown/control port. -1 to disable, Default disabled\n"
            + "   \n"
            + "   --handlerCountStartup    = set the no of worker threads to spawn at startup. Default is 5\n"
            + "   --handlerCountMax        = set the max no of worker threads to allow. Default is 40\n"
            + "   --handlerCountMaxIdle    = set the max no of idle worker threads to allow. Default is 5\n"
            + "   \n"
            + "   --sessionTimeout         = set the http session timeout value in minutes. Default to what webapp specifies, and then to 60 minutes\n"
            + "   --mimeTypes=ARG          = define additional MIME type mappings. ARG would be EXT=MIMETYPE:EXT=MIMETYPE:...\n"
            + "                              (e.g., xls=application/vnd.ms-excel:wmf=application/x-msmetafile)\n"
            + "   --maxParamCount=N        = set the max number of parameters allowed in a form submission to protect\n"
            + "                              against hash DoS attack (oCERT #2011-003). Default is 10000.\n"
            + "   --usage / --help         = show this message\n"
            + "   --version                = show the version and quit\n"
            + "   \n"
            + "Security options:\n"
            + "   --realmClassName               = Set the realm class to use for user authentication. Defaults to ArgumentsRealm class\n"
            + "   \n"
            + "   --argumentsRealm.passwd.<user> = Password for user <user>. Only valid for the ArgumentsRealm realm class\n"
            + "   --argumentsRealm.roles.<user>  = Roles for user <user> (comma separated). Only valid for the ArgumentsRealm realm class\n"
            + "   \n"
            + "   --fileRealm.configFile         = File containing users/passwds/roles. Only valid for the FileRealm realm class\n"
            + "   \n"
            + "Access logging:\n"
            + "   --accessLoggerClassName        = Set the access logger class to use for user authentication. Defaults to disabled\n"
            + "   --simpleAccessLogger.format    = The log format to use. Supports combined/common/resin/custom (SimpleAccessLogger only)\n"
            + "   --simpleAccessLogger.file      = The location pattern for the log file(SimpleAccessLogger only)");

    /*
     Set an unique cookie name.

     As can be seen in discussions like http://stackoverflow.com/questions/1146112/jsessionid-collision-between-two-servers-on-same-ip-but-different-ports
     and http://stackoverflow.com/questions/1612177/are-http-cookies-port-specific, RFC 2965 says
     cookies from one port of one host may be sent to a different port of the same host.
     This means if someone runs multiple Jenkins on different ports of the same host,
     their sessions get mixed up.

     To fix the problem, use unique session cookie name.

     This change breaks the cluster mode of Winstone, as all nodes in the cluster must share the same session cookie name.
     Jenkins doesn't support clustered operation anyway, so we need to do this here, and not in Winstone.
    */
    try {
      Field f = cl.loadClass("winstone.WinstoneSession").getField("SESSION_COOKIE_NAME");
      f.setAccessible(true);
      f.set(null, "JSESSIONID." + UUID.randomUUID().toString().replace("-", "").substring(0, 8));
    } catch (ClassNotFoundException e) {
      // no such class any more in Winstone 2.0
    }

    // run
    mainMethod.invoke(null, new Object[] {arguments.toArray(new String[0])});
  }