// Copyright (C) 2009 The Android Open Source Project // // 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.pgm.util; import static com.google.inject.Scopes.SINGLETON; import static com.google.inject.Stage.PRODUCTION; import com.google.gerrit.lifecycle.LifecycleModule; import com.google.gerrit.server.config.GerritServerConfigModule; import com.google.gerrit.server.config.SitePath; import com.google.gerrit.server.schema.DataSourceProvider; import com.google.gerrit.server.schema.DatabaseModule; import com.google.gwtorm.client.OrmException; import com.google.inject.AbstractModule; import com.google.inject.CreationException; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Module; import com.google.inject.name.Names; import com.google.inject.spi.Message; import org.apache.commons.dbcp.SQLNestedException; import org.kohsuke.args4j.Option; import java.io.File; import java.io.FileFilter; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.sql.DataSource; public abstract class SiteProgram extends AbstractProgram { @Option(name = "--site-path", aliases = {"-d"}, usage = "Local directory containing site data") private File sitePath = new File("."); /** @return the site path specified on the command line. */ protected File getSitePath() { File path = sitePath.getAbsoluteFile(); if (".".equals(path.getName())) { path = path.getParentFile(); } return path; } /** Ensures we are running inside of a valid site, otherwise throws a Die. */ protected void mustHaveValidSite() throws Die { if (!new File(new File(getSitePath(), "etc"), "gerrit.config").exists()) { throw die("not a Gerrit site: '" + getSitePath() + "'\n" + "Perhaps you need to run init first?"); } } /** Load extra JARs from {@code lib/} subdirectory of {@link #getSitePath()} */ protected void loadSiteLib() { final File libdir = new File(getSitePath(), "lib"); final File[] list = libdir.listFiles(new FileFilter() { @Override public boolean accept(File path) { if (!path.isFile()) { return false; } return path.getName().endsWith(".jar") // || path.getName().endsWith(".zip"); } }); if (list != null && 0 < list.length) { Arrays.sort(list, new Comparator<File>() { @Override public int compare(File a, File b) { return a.getName().compareTo(b.getName()); } }); addToClassLoader(list); } } private void addToClassLoader(final File[] additionalLocations) { final ClassLoader cl = getClass().getClassLoader(); if (!(cl instanceof URLClassLoader)) { throw noAddURL("Not loaded by URLClassLoader", null); } final URLClassLoader ucl = (URLClassLoader) cl; final Set<URL> have = new HashSet<URL>(); have.addAll(Arrays.asList(ucl.getURLs())); final Method m; try { m = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); m.setAccessible(true); } catch (SecurityException e) { throw noAddURL("Method addURL not available", e); } catch (NoSuchMethodException e) { throw noAddURL("Method addURL not available", e); } for (final File path : additionalLocations) { try { final URL url = path.toURI().toURL(); if (have.add(url)) { m.invoke(cl, url); } } catch (MalformedURLException e) { throw noAddURL("addURL " + path + " failed", e); } catch (IllegalArgumentException e) { throw noAddURL("addURL " + path + " failed", e); } catch (IllegalAccessException e) { throw noAddURL("addURL " + path + " failed", e); } catch (InvocationTargetException e) { throw noAddURL("addURL " + path + " failed", e.getCause()); } } } private static UnsupportedOperationException noAddURL(String m, Throwable why) { final String prefix = "Cannot extend classpath: "; return new UnsupportedOperationException(prefix + m, why); } /** @return provides database connectivity and site path. */ protected Injector createDbInjector(final DataSourceProvider.Context context) { loadSiteLib(); final File sitePath = getSitePath(); final List<Module> modules = new ArrayList<Module>(); modules.add(new AbstractModule() { @Override protected void configure() { bind(File.class).annotatedWith(SitePath.class).toInstance(sitePath); } }); modules.add(new LifecycleModule() { @Override protected void configure() { bind(DataSourceProvider.Context.class).toInstance(context); bind(Key.get(DataSource.class, Names.named("ReviewDb"))).toProvider( DataSourceProvider.class).in(SINGLETON); listener().to(DataSourceProvider.class); } }); modules.add(new GerritServerConfigModule()); modules.add(new DatabaseModule()); try { return Guice.createInjector(PRODUCTION, modules); } catch (CreationException ce) { final Message first = ce.getErrorMessages().iterator().next(); Throwable why = first.getCause(); if (why instanceof SQLException) { throw die("Cannot connect to SQL database", why); } if (why instanceof OrmException && why.getCause() != null && "Unable to determine driver URL".equals(why.getMessage())) { why = why.getCause(); if (why instanceof SQLNestedException && why.getCause() != null && why.getMessage().startsWith( "Cannot create PoolableConnectionFactory")) { throw die("Cannot connect to SQL database", why.getCause()); } throw die("Cannot connect to SQL database", why); } final StringBuilder buf = new StringBuilder(); if (why != null) { buf.append(why.getMessage()); why = why.getCause(); } else { buf.append(first.getMessage()); } while (why != null) { buf.append("\n caused by "); buf.append(why.toString()); why = why.getCause(); } throw die(buf.toString(), new RuntimeException("DbInjector failed", ce)); } } }