diff options
Diffstat (limited to 'mgrapp/src/com/google/codereview/Main.java')
-rw-r--r-- | mgrapp/src/com/google/codereview/Main.java | 284 |
1 files changed, 284 insertions, 0 deletions
diff --git a/mgrapp/src/com/google/codereview/Main.java b/mgrapp/src/com/google/codereview/Main.java new file mode 100644 index 0000000000..95a31b9488 --- /dev/null +++ b/mgrapp/src/com/google/codereview/Main.java @@ -0,0 +1,284 @@ +// Copyright 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.codereview; + +import com.google.codereview.manager.Backend; +import com.google.codereview.manager.ProjectSync; +import com.google.codereview.manager.RepositoryCache; +import com.google.codereview.manager.merge.PendingMerger; +import com.google.codereview.manager.prune.BuildPruner; +import com.google.codereview.manager.prune.BundlePruner; +import com.google.codereview.manager.unpack.ReceivedBundleUnpacker; +import com.google.codereview.rpc.HttpRpc; +import com.google.githacks.BrokenShallowRepositoryCreator; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.spearce.jgit.lib.PersonIdent; +import org.spearce.jgit.lib.RepositoryConfig; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.logging.Formatter; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; +import java.util.logging.StreamHandler; + +/** Server startup, invoked from the command line. */ +public class Main { + private static final Log LOG = LogFactory.getLog("main"); + private static final String SEC_CODEREVIEW = "codereview"; + private static final String SEC_LOG = "log"; + private static final int FOUR_HOURS = 4 * 60 * 60; // seconds + private static final int ONCE_PER_DAY = 24 * 60 * 60;// seconds + + public static void main(final String[] args) { + if (args.length == 0) { + System.err.println("usage: " + Main.class.getName() + " configfile"); + System.exit(1); + } + + final File configPath = new File(args[0]).getAbsoluteFile(); + final RepositoryConfig config = new RepositoryConfig(null, configPath); + try { + config.load(); + } catch (FileNotFoundException e) { + System.err.println("error: " + configPath + " not found"); + System.exit(1); + } catch (IOException e) { + System.err.println("error: " + configPath + " not readable"); + e.printStackTrace(System.err); + System.exit(1); + } + + configureLogging(config, args.length > 1); + LOG.info("Read " + configPath); + final Main me = new Main(configPath, config); + + if (args.length == 1) { + me.addShutdownHook(); + me.start(); + } else { + final String cmd = args[1]; + if (cmd.equals("sync")) { + new ProjectSync(me.backend).sync(); + } else if (cmd.equals("bsclone")) { + try { + final File src = new File(args[2]); + final File dst = new File(args[3]); + BrokenShallowRepositoryCreator.createRecursive(src, dst); + } catch (IOException err) { + System.err.println("error: " + err); + } + } else { + System.err.println("error: " + cmd + " not recognized"); + } + } + } + + private final RepositoryConfig config; + private final ScheduledThreadPoolExecutor pool; + private final int taskSleep; + private final Backend backend; + + private Main(final File configPath, final RepositoryConfig rc) { + config = rc; + + final int threads = config.getInt(SEC_CODEREVIEW, "threads", 10); + taskSleep = config.getInt(SEC_CODEREVIEW, "sleep", 10); + LOG.info("Starting thread pool with " + threads + " initial threads."); + pool = new ScheduledThreadPoolExecutor(threads); + + final RepositoryCache repoCache = createRepositoryCache(); + final HttpRpc rpc = createHttpRpc(configPath.getParentFile()); + backend = new Backend(repoCache, rpc, pool, createUserPersonIdent()); + } + + private RepositoryCache createRepositoryCache() { + final File basedir = new File(required(SEC_CODEREVIEW, "basedir")); + return new RepositoryCache(basedir); + } + + private HttpRpc createHttpRpc(final File base) throws ThreadDeath { + final URL serverUrl; + try { + serverUrl = new URL(required(SEC_CODEREVIEW, "server")); + } catch (MalformedURLException err) { + System.err.println("error: Bad URL in " + SEC_CODEREVIEW + ".server"); + System.exit(1); + throw new ThreadDeath(); + } + + final String roleUser = config.getString(SEC_CODEREVIEW, null, "username"); + File pwf = new File(required(SEC_CODEREVIEW, "secureconfig")); + if (!pwf.isAbsolute()) { + pwf = new File(base, pwf.getPath()); + } + + final RepositoryConfig pwc = new RepositoryConfig(null, pwf); + try { + pwc.load(); + } catch (IOException e) { + System.err.println("error: Cannot read secureconfig: " + pwf + ": " + + e.getMessage()); + System.exit(1); + throw new ThreadDeath(); + } + + final String rolePass = pwc.getString(SEC_CODEREVIEW, null, "password"); + final String apiKey = pwc.getString(SEC_CODEREVIEW, null, "internalapikey"); + return new HttpRpc(serverUrl, roleUser, rolePass, apiKey); + } + + private PersonIdent createUserPersonIdent() { + final String name = required("user", "name"); + final String email = required("user", "email"); + return new PersonIdent(name, email, System.currentTimeMillis(), 0); + } + + private void addShutdownHook() { + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + LOG.info("Shutting down thread pool."); + pool.shutdownNow(); + } + }); + } + + private void start() { + schedule(new ReceivedBundleUnpacker(backend)); + schedule(new PendingMerger(backend)); + schedule(new BundlePruner(backend), FOUR_HOURS); + schedule(new BuildPruner(backend), ONCE_PER_DAY); + } + + private void schedule(final Runnable t) { + schedule(t, taskSleep); + } + + private void schedule(final Runnable t, final int sleep) { + pool.scheduleWithFixedDelay(t, 0, sleep, TimeUnit.SECONDS); + } + + private String required(final String sec, final String key) { + final String r = config.getString(sec, null, key); + if (r == null || r.length() == 0) { + System.err.println("error: Missing required config " + sec + "." + key); + System.exit(1); + } + return r; + } + + private static void configureLogging(final RepositoryConfig config, + final boolean interactive) { + final Logger root = Logger.getLogger(""); + for (final Handler h : root.getHandlers()) + root.removeHandler(h); + + OutputStream out = System.out; + final String logfile = config.getString(SEC_LOG, null, "file"); + if (logfile != null && !interactive) { + try { + out = new FileOutputStream(logfile, true); + } catch (IOException err) { + System.err.println("error: Cannot append to " + logfile); + System.err.println("error: " + err.toString()); + System.exit(1); + throw new ThreadDeath(); + } + } + + final StreamHandler ch = new StreamHandler(out, new Formatter() { + private final SimpleDateFormat sdf; + private final StringWriter stringBuffer = new StringWriter(); + private final PrintWriter p = new PrintWriter(stringBuffer); + { + sdf = new SimpleDateFormat("yyyyMMdd.HHmmss"); + } + + @Override + public String format(final LogRecord record) { + stringBuffer.getBuffer().setLength(0); + final String levelName = record.getLevel().getName(); + p.print(sdf.format(new Date(record.getMillis()))); + p.print(' '); + p.print(levelName); + for (int cnt = "WARNING".length() - levelName.length(); --cnt >= 0;) { + p.print(' '); + } + p.print(' '); + p.print(record.getLoggerName()); + p.print(" - "); + p.print(record.getMessage()); + p.print('\n'); + if (record.getThrown() != null) { + record.getThrown().printStackTrace(p); + p.print('\n'); + } + p.flush(); + return stringBuffer.toString(); + } + }) { + @Override + public synchronized void publish(final LogRecord record) { + super.publish(record); + flush(); + } + }; + root.addHandler(ch); + + final Level levelObj; + final String levelStr = config.getString(SEC_LOG, null, "level"); + if (levelStr == null || levelStr.length() == 0) { + levelObj = Level.INFO; + } else if ("trace".equalsIgnoreCase(levelStr)) { + levelObj = Level.FINEST; + } else if ("debug".equalsIgnoreCase(levelStr)) { + levelObj = Level.FINE; + } else if ("info".equalsIgnoreCase(levelStr)) { + levelObj = Level.INFO; + } else if ("warning".equalsIgnoreCase(levelStr) + || "warn".equalsIgnoreCase(levelStr)) { + levelObj = Level.WARNING; + } else if ("error".equalsIgnoreCase(levelStr)) { + levelObj = Level.SEVERE; + } else if ("fatal".equalsIgnoreCase(levelStr)) { + levelObj = Level.SEVERE; + } else { + System.out.println("warning: Bad " + SEC_LOG + ".level " + levelStr + + "; assuming info"); + levelObj = Level.INFO; + } + ch.setLevel(levelObj); + root.setLevel(levelObj); + + Logger.getLogger("httpclient").setLevel(Level.WARNING); + Logger.getLogger("org.apache.commons.httpclient").setLevel(Level.WARNING); + } +} |