blob: 95a31b948805d329b6d31fb56f67bf5698090168 [file] [log] [blame]
// 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);
}
}