Rewrite our build as modular maven components

This refactoring splits the code up into different components, with
their own per-component CLASSPATH.  By moving all of our classes
into isolated components we can better isolate the classpaths and
try to avoid unexpected dependency problems.  It also allows us to
more clearly define which components are used by the GWT UI and
thus must be compiled under GWT, and which components are run on
the server and can therefore use more of the J2SE API.

Change-Id: I833cc22bacc5655d1c9099ed7c2b0e0a5b08855a
Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
Shawn O. Pearce
2009-11-07 12:55:26 -08:00
parent 2464ac82b7
commit 44671f5c69
719 changed files with 9467 additions and 2417 deletions

View File

@@ -0,0 +1,419 @@
/*
* 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.gerrit.gwtdebug;
import com.google.gwt.core.ext.ServletContainer;
import com.google.gwt.core.ext.ServletContainerLauncher;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.dev.shell.jetty.JettyNullLogger;
import org.mortbay.component.AbstractLifeCycle;
import org.mortbay.jetty.AbstractConnector;
import org.mortbay.jetty.Request;
import org.mortbay.jetty.RequestLog;
import org.mortbay.jetty.Response;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.HttpFields.Field;
import org.mortbay.jetty.handler.RequestLogHandler;
import org.mortbay.jetty.nio.SelectChannelConnector;
import org.mortbay.jetty.webapp.WebAppClassLoader;
import org.mortbay.jetty.webapp.WebAppContext;
import org.mortbay.log.Log;
import org.mortbay.log.Logger;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Iterator;
public class GerritDebugLauncher extends ServletContainerLauncher {
/**
* Log jetty requests/responses to TreeLogger.
*/
public static class JettyRequestLogger extends AbstractLifeCycle implements
RequestLog {
private final TreeLogger logger;
public JettyRequestLogger(TreeLogger logger) {
this.logger = logger;
}
/**
* Log an HTTP request/response to TreeLogger.
*/
@SuppressWarnings("unchecked")
public void log(Request request, Response response) {
int status = response.getStatus();
if (status < 0) {
// Copied from NCSARequestLog
status = 404;
}
TreeLogger.Type logStatus, logHeaders;
if (status >= 500) {
logStatus = TreeLogger.ERROR;
logHeaders = TreeLogger.INFO;
} else if (status >= 400) {
logStatus = TreeLogger.WARN;
logHeaders = TreeLogger.INFO;
} else {
logStatus = TreeLogger.INFO;
logHeaders = TreeLogger.DEBUG;
}
String userString = request.getRemoteUser();
if (userString == null) {
userString = "";
} else {
userString += "@";
}
String bytesString = "";
if (response.getContentCount() > 0) {
bytesString = " " + response.getContentCount() + " bytes";
}
if (logger.isLoggable(logStatus)) {
TreeLogger branch =
logger.branch(logStatus, String.valueOf(status) + " - "
+ request.getMethod() + ' ' + request.getUri() + " ("
+ userString + request.getRemoteHost() + ')' + bytesString);
if (branch.isLoggable(logHeaders)) {
// Request headers
TreeLogger headers = branch.branch(logHeaders, "Request headers");
Iterator<Field> headerFields =
request.getConnection().getRequestFields().getFields();
while (headerFields.hasNext()) {
Field headerField = headerFields.next();
headers.log(logHeaders, headerField.getName() + ": "
+ headerField.getValue());
}
// Response headers
headers = branch.branch(logHeaders, "Response headers");
headerFields = response.getHttpFields().getFields();
while (headerFields.hasNext()) {
Field headerField = headerFields.next();
headers.log(logHeaders, headerField.getName() + ": "
+ headerField.getValue());
}
}
}
}
}
/**
* An adapter for the Jetty logging system to GWT's TreeLogger. This
* implementation class is only public to allow {@link Log} to instantiate it.
*
* The weird static data / default construction setup is a game we play with
* {@link Log}'s static initializer to prevent the initial log message from
* going to stderr.
*/
public static class JettyTreeLogger implements Logger {
private final TreeLogger logger;
public JettyTreeLogger(TreeLogger logger) {
if (logger == null) {
throw new NullPointerException();
}
this.logger = logger;
}
public void debug(String msg, Object arg0, Object arg1) {
logger.log(TreeLogger.SPAM, format(msg, arg0, arg1));
}
public void debug(String msg, Throwable th) {
logger.log(TreeLogger.SPAM, msg, th);
}
public Logger getLogger(String name) {
return this;
}
public void info(String msg, Object arg0, Object arg1) {
logger.log(TreeLogger.INFO, format(msg, arg0, arg1));
}
public boolean isDebugEnabled() {
return logger.isLoggable(TreeLogger.SPAM);
}
public void setDebugEnabled(boolean enabled) {
// ignored
}
public void warn(String msg, Object arg0, Object arg1) {
logger.log(TreeLogger.WARN, format(msg, arg0, arg1));
}
public void warn(String msg, Throwable th) {
logger.log(TreeLogger.WARN, msg, th);
}
/**
* Copied from org.mortbay.log.StdErrLog.
*/
private String format(String msg, Object arg0, Object arg1) {
int i0 = msg.indexOf("{}");
int i1 = i0 < 0 ? -1 : msg.indexOf("{}", i0 + 2);
if (arg1 != null && i1 >= 0) {
msg = msg.substring(0, i1) + arg1 + msg.substring(i1 + 2);
}
if (arg0 != null && i0 >= 0) {
msg = msg.substring(0, i0) + arg0 + msg.substring(i0 + 2);
}
return msg;
}
}
/**
* The resulting {@link ServletContainer} this is launched.
*/
protected static class JettyServletContainer extends ServletContainer {
private final int actualPort;
private final File appRootDir;
private final TreeLogger logger;
private final Server server;
private final WebAppContext wac;
public JettyServletContainer(TreeLogger logger, Server server,
WebAppContext wac, int actualPort, File appRootDir) {
this.logger = logger;
this.server = server;
this.wac = wac;
this.actualPort = actualPort;
this.appRootDir = appRootDir;
}
@Override
public int getPort() {
return actualPort;
}
@Override
public void refresh() throws UnableToCompleteException {
String msg =
"Reloading web app to reflect changes in "
+ appRootDir.getAbsolutePath();
TreeLogger branch = logger.branch(TreeLogger.INFO, msg);
// Temporarily log Jetty on the branch.
Log.setLog(new JettyTreeLogger(branch));
try {
wac.stop();
wac.start();
branch.log(TreeLogger.INFO, "Reload completed successfully");
} catch (Exception e) {
branch.log(TreeLogger.ERROR, "Unable to restart embedded Jetty server",
e);
throw new UnableToCompleteException();
} finally {
// Reset the top-level logger.
Log.setLog(new JettyTreeLogger(logger));
}
}
@Override
public void stop() throws UnableToCompleteException {
TreeLogger branch =
logger.branch(TreeLogger.INFO, "Stopping Jetty server");
// Temporarily log Jetty on the branch.
Log.setLog(new JettyTreeLogger(branch));
try {
server.stop();
server.setStopAtShutdown(false);
branch.log(TreeLogger.INFO, "Stopped successfully");
} catch (Exception e) {
branch.log(TreeLogger.ERROR, "Unable to stop embedded Jetty server", e);
throw new UnableToCompleteException();
} finally {
// Reset the top-level logger.
Log.setLog(new JettyTreeLogger(logger));
}
}
}
/**
* A {@link WebAppContext} tailored to GWT hosted mode. Features hot-reload
* with a new {@link WebAppClassLoader} to pick up disk changes. The default
* Jetty {@code WebAppContext} will create new instances of servlets, but it
* will not create a brand new {@link ClassLoader}. By creating a new {@code
* ClassLoader} each time, we re-read updated classes from disk.
*
* Also provides special class filtering to isolate the web app from the GWT
* hosting environment.
*/
protected final class MyWebAppContext extends WebAppContext {
/**
* Parent ClassLoader for the Jetty web app, which can only load JVM
* classes. We would just use <code>null</code> for the parent ClassLoader
* except this makes Jetty unhappy.
*/
private final ClassLoader bootStrapOnlyClassLoader =
new ClassLoader(null) {};
private final ClassLoader systemClassLoader =
Thread.currentThread().getContextClassLoader();
private final TreeLogger logger;
@SuppressWarnings("unchecked")
private MyWebAppContext(TreeLogger logger, String webApp, String contextPath) {
super(webApp, contextPath);
this.logger = logger;
// Prevent file locking on Windows; pick up file changes.
getInitParams().put(
"org.mortbay.jetty.servlet.Default.useFileMappedBuffer", "false");
// Since the parent class loader is bootstrap-only, prefer it first.
setParentLoaderPriority(true);
}
@Override
protected void doStart() throws Exception {
setClassLoader(new MyLoader());
super.doStart();
}
@Override
protected void doStop() throws Exception {
super.doStop();
setClassLoader(null);
}
private class MyLoader extends WebAppClassLoader {
MyLoader() throws IOException {
super(bootStrapOnlyClassLoader, MyWebAppContext.this);
final URLClassLoader scl = (URLClassLoader) systemClassLoader;
final URL[] urls = scl.getURLs();
for (URL u : urls) {
if ("file".equals(u.getProtocol())) {
addClassPath(u.getPath());
}
}
}
@Override
public boolean isSystemPath(String name) {
name = name.replace('/', '.');
return super.isSystemPath(name) //
|| name.startsWith("org.bouncycastle.");
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// For system path, always prefer the outside world.
if (isSystemPath(name)) {
try {
return systemClassLoader.loadClass(name);
} catch (ClassNotFoundException e) {
}
}
return super.findClass(name);
}
}
}
static {
// Suppress spammy Jetty log initialization.
System
.setProperty("org.mortbay.log.class", JettyNullLogger.class.getName());
Log.getLog();
/*
* Make JDT the default Ant compiler so that JSP compilation just works
* out-of-the-box. If we don't set this, it's very, very difficult to make
* JSP compilation work.
*/
String antJavaC =
System.getProperty("build.compiler",
"org.eclipse.jdt.core.JDTCompilerAdapter");
System.setProperty("build.compiler", antJavaC);
}
@Override
public ServletContainer start(TreeLogger logger, int port, File warDir)
throws Exception {
TreeLogger branch =
logger.branch(TreeLogger.INFO, "Starting Jetty on port " + port, null);
checkStartParams(branch, port, warDir);
// Setup our branch logger during startup.
Log.setLog(new JettyTreeLogger(branch));
// Turn off XML validation.
System.setProperty("org.mortbay.xml.XmlParser.Validating", "false");
AbstractConnector connector = getConnector();
connector.setPort(port);
// Don't share ports with an existing process.
connector.setReuseAddress(false);
// Linux keeps the port blocked after shutdown if we don't disable this.
connector.setSoLingerTime(0);
Server server = new Server();
server.addConnector(connector);
// warDir is "$top/gerrit-gwtui/target/gwt-hosted-mode"
//
File top = warDir.getParentFile().getParentFile().getParentFile();
File app = new File(top, "gerrit-war/src/main/webapp");
File webxml = new File(app, "WEB-INF/web.xml");
// Create a new web app in the war directory.
//
WebAppContext wac =
new MyWebAppContext(logger, warDir.getAbsolutePath(), "/");
wac.setDescriptor(webxml.getAbsolutePath());
RequestLogHandler logHandler = new RequestLogHandler();
logHandler.setRequestLog(new JettyRequestLogger(logger));
logHandler.setHandler(wac);
server.setHandler(logHandler);
server.start();
server.setStopAtShutdown(true);
// Now that we're started, log to the top level logger.
Log.setLog(new JettyTreeLogger(logger));
return new JettyServletContainer(logger, server, wac, connector
.getLocalPort(), warDir);
}
protected AbstractConnector getConnector() {
return new SelectChannelConnector();
}
private void checkStartParams(TreeLogger logger, int port, File appRootDir) {
if (logger == null) {
throw new NullPointerException("logger cannot be null");
}
if (port < 0 || port > 65535) {
throw new IllegalArgumentException(
"port must be either 0 (for auto) or less than 65536");
}
if (appRootDir == null) {
throw new NullPointerException("app root direcotry cannot be null");
}
}
}