| // Copyright (C) 2008 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.server; |
| |
| import com.google.gerrit.client.data.AccountCache; |
| import com.google.gerrit.client.data.ApprovalType; |
| import com.google.gerrit.client.data.GerritConfig; |
| import com.google.gerrit.client.data.GitwebLink; |
| import com.google.gerrit.client.data.GroupCache; |
| import com.google.gerrit.client.data.ProjectCache; |
| import com.google.gerrit.client.reviewdb.AccountGroup; |
| import com.google.gerrit.client.reviewdb.ApprovalCategory; |
| import com.google.gerrit.client.reviewdb.ApprovalCategoryValue; |
| import com.google.gerrit.client.reviewdb.Branch; |
| import com.google.gerrit.client.reviewdb.Change; |
| import com.google.gerrit.client.reviewdb.Project; |
| import com.google.gerrit.client.reviewdb.ProjectRight; |
| import com.google.gerrit.client.reviewdb.ReviewDb; |
| import com.google.gerrit.client.reviewdb.SchemaVersion; |
| import com.google.gerrit.client.reviewdb.SystemConfig; |
| import com.google.gerrit.client.reviewdb.TrustedExternalId; |
| import com.google.gerrit.client.reviewdb.SystemConfig.LoginType; |
| import com.google.gerrit.client.rpc.Common; |
| import com.google.gerrit.client.workflow.NoOpFunction; |
| import com.google.gerrit.client.workflow.SubmitFunction; |
| import com.google.gerrit.git.MergeQueue; |
| import com.google.gerrit.git.PushAllProjectsOp; |
| import com.google.gerrit.git.PushQueue; |
| import com.google.gerrit.git.RepositoryCache; |
| import com.google.gerrit.git.WorkQueue; |
| import com.google.gerrit.server.mail.EmailException; |
| import com.google.gerrit.server.patch.DiffCacheEntryFactory; |
| import com.google.gerrit.server.ssh.SshKeyCacheEntryFactory; |
| import com.google.gwtjsonrpc.server.SignedToken; |
| import com.google.gwtjsonrpc.server.XsrfException; |
| import com.google.gwtorm.client.OrmException; |
| import com.google.gwtorm.client.Transaction; |
| import com.google.gwtorm.jdbc.Database; |
| import com.google.gwtorm.jdbc.SimpleDataSource; |
| |
| import net.sf.ehcache.Cache; |
| import net.sf.ehcache.CacheManager; |
| import net.sf.ehcache.Ehcache; |
| import net.sf.ehcache.config.CacheConfiguration; |
| import net.sf.ehcache.config.Configuration; |
| import net.sf.ehcache.config.DiskStoreConfiguration; |
| import net.sf.ehcache.constructs.blocking.SelfPopulatingCache; |
| import net.sf.ehcache.store.MemoryStoreEvictionPolicy; |
| |
| import org.apache.commons.codec.binary.Base64; |
| import org.apache.commons.net.smtp.AuthSMTPClient; |
| import org.apache.commons.net.smtp.SMTPClient; |
| import org.apache.commons.net.smtp.SMTPReply; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| import org.spearce.jgit.lib.PersonIdent; |
| import org.spearce.jgit.lib.RepositoryConfig; |
| import org.spearce.jgit.lib.WindowCache; |
| import org.spearce.jgit.lib.WindowCacheConfig; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.sql.SQLException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.concurrent.TimeUnit; |
| |
| import javax.naming.InitialContext; |
| import javax.naming.NamingException; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.sql.DataSource; |
| |
| /** Global server-side state for Gerrit. */ |
| public class GerritServer { |
| private static final Logger log = LoggerFactory.getLogger(GerritServer.class); |
| private static DataSource datasource; |
| private static GerritServer impl; |
| private static CacheManager cacheMgr; |
| |
| static void closeDataSource() { |
| if (datasource != null) { |
| try { |
| try { |
| Class.forName("com.mchange.v2.c3p0.DataSources").getMethod("destroy", |
| DataSource.class).invoke(null, datasource); |
| } catch (Throwable bad) { |
| // Oh well, its not a c3p0 pooled connection. Too bad its |
| // not standardized how "good applications cleanup". |
| } |
| } finally { |
| datasource = null; |
| } |
| } |
| |
| if (cacheMgr != null) { |
| try { |
| cacheMgr.shutdown(); |
| } catch (Throwable bad) { |
| } finally { |
| cacheMgr = null; |
| } |
| } |
| } |
| |
| /** |
| * Obtain the singleton server instance for this web application. |
| * |
| * @return the server instance. Never null. |
| * @throws OrmException the database could not be configured. There is |
| * something wrong with the schema configuration in {@link ReviewDb} |
| * that must be addressed by a developer. |
| * @throws XsrfException the XSRF support could not be correctly configured to |
| * protect the application against cross-site request forgery. The JVM |
| * is most likely lacking critical security algorithms. |
| */ |
| public static synchronized GerritServer getInstance() throws OrmException, |
| XsrfException { |
| return getInstance(true); |
| } |
| |
| public static synchronized GerritServer getInstance(final boolean startQueues) |
| throws OrmException, XsrfException { |
| if (impl == null) { |
| try { |
| impl = new GerritServer(); |
| if (startQueues) { |
| impl.reloadSubmitQueue(); |
| if (PushQueue.isReplicationEnabled()) { |
| WorkQueue.schedule(new PushAllProjectsOp(), 30, TimeUnit.SECONDS); |
| } |
| } |
| } catch (OrmException e) { |
| closeDataSource(); |
| log.error("GerritServer ORM is unavailable", e); |
| throw e; |
| } catch (XsrfException e) { |
| closeDataSource(); |
| log.error("GerritServer XSRF support failed to initailize", e); |
| throw e; |
| } |
| } |
| return impl; |
| } |
| |
| public static String serverUrl(final HttpServletRequest req) { |
| // Assume this servlet is in the context with a simple name like "login" |
| // and we were accessed without any path info. Clipping the last part of |
| // the name from the URL should generate the web application's root path. |
| // |
| String uri = req.getRequestURL().toString(); |
| final int s = uri.lastIndexOf('/'); |
| if (s >= 0) { |
| uri = uri.substring(0, s + 1); |
| } |
| final String sfx = "/gerrit/rpc/"; |
| if (uri.endsWith(sfx)) { |
| // Nope, it was one of our RPC servlets. Drop the rpc too. |
| // |
| uri = uri.substring(0, uri.length() - (sfx.length() - 1)); |
| } |
| return uri; |
| } |
| |
| private final Database<ReviewDb> db; |
| private final RepositoryConfig gerritConfigFile; |
| private final int sessionAge; |
| private SystemConfig sConfig; |
| private final SignedToken xsrf; |
| private final SignedToken account; |
| private final SignedToken emailReg; |
| private final RepositoryCache repositories; |
| private final SelfPopulatingCache diffCache; |
| private final SelfPopulatingCache sshKeysCache; |
| |
| private GerritServer() throws OrmException, XsrfException { |
| db = createDatabase(); |
| loadSystemConfig(); |
| if (sConfig == null) { |
| throw new OrmException("No " + SystemConfig.class.getName() + " found"); |
| } |
| |
| final File cfgLoc = new File(getSitePath(), "gerrit.config"); |
| gerritConfigFile = new RepositoryConfig(null, cfgLoc); |
| try { |
| gerritConfigFile.load(); |
| } catch (FileNotFoundException e) { |
| log.info("No " + cfgLoc.getAbsolutePath() + "; assuming defaults"); |
| } catch (IOException e) { |
| throw new OrmException("Cannot read " + cfgLoc.getAbsolutePath(), e); |
| } |
| reconfigureWindowCache(); |
| sessionAge = gerritConfigFile.getInt("auth", "maxsessionage", 12 * 60) * 60; |
| |
| xsrf = new SignedToken(getSessionAge(), sConfig.xsrfPrivateKey); |
| |
| final int accountCookieAge; |
| switch (getLoginType()) { |
| case HTTP: |
| accountCookieAge = -1; // expire when the browser closes |
| break; |
| case OPENID: |
| default: |
| accountCookieAge = getSessionAge(); |
| break; |
| } |
| account = new SignedToken(accountCookieAge, sConfig.accountPrivateKey); |
| emailReg = new SignedToken(5 * 24 * 60 * 60, sConfig.accountPrivateKey); |
| |
| final String basePath = |
| getGerritConfig().getString("gerrit", null, "basepath"); |
| if (basePath != null) { |
| File root = new File(basePath); |
| if (!root.isAbsolute()) { |
| root = new File(getSitePath(), basePath); |
| } |
| repositories = new RepositoryCache(root); |
| } else { |
| repositories = null; |
| } |
| |
| final ReviewDb c = db.open(); |
| try { |
| loadGerritConfig(c); |
| } finally { |
| c.close(); |
| } |
| |
| Common.setSchemaFactory(db); |
| Common.setProjectCache(new ProjectCache()); |
| Common.setAccountCache(new AccountCache()); |
| Common.setGroupCache(new GroupCache(sConfig)); |
| |
| cacheMgr = new CacheManager(createCacheConfiguration()); |
| diffCache = startCacheDiff(); |
| sshKeysCache = startCacheSshKeys(); |
| } |
| |
| private Configuration createCacheConfiguration() { |
| final Configuration mgrCfg = new Configuration(); |
| configureDiskStore(mgrCfg); |
| configureDefaultCache(mgrCfg); |
| |
| if (getLoginType() == LoginType.OPENID) { |
| final CacheConfiguration c; |
| c = configureNamedCache(mgrCfg, "openid", false, 5); |
| c.setTimeToLiveSeconds(c.getTimeToIdleSeconds()); |
| mgrCfg.addCache(c); |
| } |
| |
| mgrCfg.addCache(configureNamedCache(mgrCfg, "diff", true, 0)); |
| mgrCfg.addCache(configureNamedCache(mgrCfg, "sshkeys", false, 0)); |
| return mgrCfg; |
| } |
| |
| private void configureDiskStore(final Configuration mgrCfg) { |
| String path = gerritConfigFile.getString("cache", null, "directory"); |
| if (path == null || path.length() == 0) { |
| path = "disk_cache"; |
| } |
| |
| final File loc = new File(getSitePath(), path); |
| if (loc.exists() || loc.mkdirs()) { |
| if (loc.canWrite()) { |
| final DiskStoreConfiguration c = new DiskStoreConfiguration(); |
| c.setPath(loc.getAbsolutePath()); |
| mgrCfg.addDiskStore(c); |
| log.info("Enabling disk cache " + loc.getAbsolutePath()); |
| } else { |
| log.warn("Can't write to disk cache: " + loc.getAbsolutePath()); |
| } |
| } else { |
| log.warn("Can't create disk cache: " + loc.getAbsolutePath()); |
| } |
| } |
| |
| private void configureDefaultCache(final Configuration mgrCfg) { |
| final RepositoryConfig i = gerritConfigFile; |
| final CacheConfiguration c = new CacheConfiguration(); |
| |
| c.setMaxElementsInMemory(i.getInt("cache", "memorylimit", 1024)); |
| c.setMemoryStoreEvictionPolicyFromObject(MemoryStoreEvictionPolicy.LFU); |
| |
| c.setTimeToLiveSeconds(0); |
| final int oneday = 24 * 60; |
| c.setTimeToIdleSeconds(i.getInt("cache", "maxage", 3 * 30 * oneday) * 60); |
| c.setEternal(c.getTimeToIdleSeconds() == 0); |
| |
| if (mgrCfg.getDiskStoreConfiguration() != null) { |
| c.setMaxElementsOnDisk(i.getInt("cache", "disklimit", 16384)); |
| c.setOverflowToDisk(false); |
| c.setDiskPersistent(false); |
| |
| int diskbuffer = i.getInt("cache", "diskbuffer", 5 * 1024 * 1024); |
| diskbuffer /= 1024 * 1024; |
| c.setDiskSpoolBufferSizeMB(Math.max(1, diskbuffer)); |
| c.setDiskExpiryThreadIntervalSeconds(60 * 60); |
| } |
| |
| mgrCfg.setDefaultCacheConfiguration(c); |
| } |
| |
| private CacheConfiguration configureNamedCache(final Configuration mgrCfg, |
| final String name, final boolean disk, final int defaultAge) { |
| final RepositoryConfig i = gerritConfigFile; |
| final CacheConfiguration def = mgrCfg.getDefaultCacheConfiguration(); |
| final CacheConfiguration cfg; |
| try { |
| cfg = def.clone(); |
| } catch (CloneNotSupportedException e) { |
| throw new RuntimeException("Cannot configure cache " + name, e); |
| } |
| cfg.setName(name); |
| |
| cfg.setMaxElementsInMemory(i.getInt("cache", name, "memorylimit", def |
| .getMaxElementsInMemory())); |
| |
| cfg.setTimeToIdleSeconds(i.getInt("cache", name, "maxage", defaultAge > 0 |
| ? defaultAge : (int) (def.getTimeToIdleSeconds() / 60)) * 60); |
| cfg.setEternal(cfg.getTimeToIdleSeconds() == 0); |
| |
| if (disk && mgrCfg.getDiskStoreConfiguration() != null) { |
| cfg.setMaxElementsOnDisk(i.getInt("cache", name, "disklimit", def |
| .getMaxElementsOnDisk())); |
| |
| final int m = 1024 * 1024; |
| final int diskbuffer = |
| i.getInt("cache", name, "diskbuffer", def.getDiskSpoolBufferSizeMB() |
| * m) |
| / m; |
| cfg.setDiskSpoolBufferSizeMB(Math.max(1, diskbuffer)); |
| cfg.setOverflowToDisk(true); |
| cfg.setDiskPersistent(true); |
| } |
| |
| return cfg; |
| } |
| |
| private SelfPopulatingCache startCacheDiff() { |
| final Cache dc = cacheMgr.getCache("diff"); |
| final SelfPopulatingCache r; |
| |
| r = new SelfPopulatingCache(dc, new DiffCacheEntryFactory(this)); |
| cacheMgr.replaceCacheWithDecoratedCache(dc, r); |
| return r; |
| } |
| |
| private SelfPopulatingCache startCacheSshKeys() { |
| final Cache dc = cacheMgr.getCache("sshkeys"); |
| final SelfPopulatingCache r; |
| |
| r = new SelfPopulatingCache(dc, new SshKeyCacheEntryFactory()); |
| cacheMgr.replaceCacheWithDecoratedCache(dc, r); |
| return r; |
| } |
| |
| public static Database<ReviewDb> createDatabase() throws OrmException { |
| final String dsName = "java:comp/env/jdbc/ReviewDb"; |
| try { |
| datasource = (DataSource) new InitialContext().lookup(dsName); |
| } catch (NamingException namingErr) { |
| final Properties p = readGerritDataSource(); |
| if (p == null) { |
| throw new OrmException("Initialization error:\n" + " * No DataSource " |
| + dsName + "\n" + " * No -DGerritServer=GerritServer.properties" |
| + " on Java command line", namingErr); |
| } |
| |
| try { |
| datasource = new SimpleDataSource(p); |
| } catch (SQLException se) { |
| throw new OrmException("Database unavailable", se); |
| } |
| } |
| return new Database<ReviewDb>(datasource, ReviewDb.class); |
| } |
| |
| private static Properties readGerritDataSource() throws OrmException { |
| final Properties srvprop = new Properties(); |
| String name = System.getProperty("GerritServer"); |
| if (name == null) { |
| name = "GerritServer.properties"; |
| } |
| try { |
| final InputStream in = new FileInputStream(name); |
| try { |
| srvprop.load(in); |
| } finally { |
| in.close(); |
| } |
| } catch (IOException e) { |
| throw new OrmException("Cannot read " + name, e); |
| } |
| |
| final Properties dbprop = new Properties(); |
| for (final Map.Entry<Object, Object> e : srvprop.entrySet()) { |
| final String key = (String) e.getKey(); |
| if (key.startsWith("database.")) { |
| dbprop.put(key.substring("database.".length()), e.getValue()); |
| } |
| } |
| return dbprop; |
| } |
| |
| private void initSystemConfig(final ReviewDb c) throws OrmException { |
| final AccountGroup admin = |
| new AccountGroup(new AccountGroup.NameKey("Administrators"), |
| new AccountGroup.Id(c.nextAccountGroupId())); |
| admin.setDescription("Gerrit Site Administrators"); |
| c.accountGroups().insert(Collections.singleton(admin)); |
| |
| final AccountGroup anonymous = |
| new AccountGroup(new AccountGroup.NameKey("Anonymous Users"), |
| new AccountGroup.Id(c.nextAccountGroupId())); |
| anonymous.setDescription("Any user, signed-in or not"); |
| anonymous.setOwnerGroupId(admin.getId()); |
| c.accountGroups().insert(Collections.singleton(anonymous)); |
| |
| final AccountGroup registered = |
| new AccountGroup(new AccountGroup.NameKey("Registered Users"), |
| new AccountGroup.Id(c.nextAccountGroupId())); |
| registered.setDescription("Any signed-in user"); |
| registered.setOwnerGroupId(admin.getId()); |
| c.accountGroups().insert(Collections.singleton(registered)); |
| |
| final SystemConfig s = SystemConfig.create(); |
| s.xsrfPrivateKey = SignedToken.generateRandomKey(); |
| s.accountPrivateKey = SignedToken.generateRandomKey(); |
| s.adminGroupId = admin.getId(); |
| s.anonymousGroupId = anonymous.getId(); |
| s.registeredGroupId = registered.getId(); |
| c.systemConfig().insert(Collections.singleton(s)); |
| |
| // By default with OpenID trust any http:// or https:// provider |
| // |
| initTrustedExternalId(c, "http://"); |
| initTrustedExternalId(c, "https://"); |
| initTrustedExternalId(c, "https://www.google.com/accounts/o8/id?id="); |
| } |
| |
| private void initTrustedExternalId(final ReviewDb c, final String re) |
| throws OrmException { |
| c.trustedExternalIds().insert( |
| Collections.singleton(new TrustedExternalId(new TrustedExternalId.Key( |
| re)))); |
| } |
| |
| private void initWildCardProject(final ReviewDb c) throws OrmException { |
| final Project proj; |
| |
| proj = |
| new Project(new Project.NameKey("-- All Projects --"), |
| ProjectRight.WILD_PROJECT); |
| proj.setDescription("Rights inherited by all other projects"); |
| proj.setOwnerGroupId(sConfig.adminGroupId); |
| proj.setUseContributorAgreements(false); |
| c.projects().insert(Collections.singleton(proj)); |
| } |
| |
| private void initVerifiedCategory(final ReviewDb c) throws OrmException { |
| final Transaction txn = c.beginTransaction(); |
| final ApprovalCategory cat; |
| final ArrayList<ApprovalCategoryValue> vals; |
| |
| cat = new ApprovalCategory(new ApprovalCategory.Id("VRIF"), "Verified"); |
| cat.setPosition((short) 0); |
| vals = new ArrayList<ApprovalCategoryValue>(); |
| vals.add(value(cat, 1, "Verified")); |
| vals.add(value(cat, 0, "No score")); |
| vals.add(value(cat, -1, "Fails")); |
| c.approvalCategories().insert(Collections.singleton(cat), txn); |
| c.approvalCategoryValues().insert(vals, txn); |
| txn.commit(); |
| } |
| |
| private void initCodeReviewCategory(final ReviewDb c) throws OrmException { |
| final Transaction txn = c.beginTransaction(); |
| final ApprovalCategory cat; |
| final ArrayList<ApprovalCategoryValue> vals; |
| |
| cat = new ApprovalCategory(new ApprovalCategory.Id("CRVW"), "Code Review"); |
| cat.setPosition((short) 1); |
| vals = new ArrayList<ApprovalCategoryValue>(); |
| vals.add(value(cat, 2, "Looks good to me, approved")); |
| vals.add(value(cat, 1, "Looks good to me, but someone else must approve")); |
| vals.add(value(cat, 0, "No score")); |
| vals.add(value(cat, -1, "I would prefer that you didn't submit this")); |
| vals.add(value(cat, -2, "Do not submit")); |
| c.approvalCategories().insert(Collections.singleton(cat), txn); |
| c.approvalCategoryValues().insert(vals, txn); |
| txn.commit(); |
| |
| final ProjectRight approve = |
| new ProjectRight(new ProjectRight.Key(ProjectRight.WILD_PROJECT, cat |
| .getId(), sConfig.registeredGroupId)); |
| approve.setMaxValue((short) 1); |
| approve.setMinValue((short) -1); |
| c.projectRights().insert(Collections.singleton(approve)); |
| } |
| |
| private void initReadCategory(final ReviewDb c) throws OrmException { |
| final Transaction txn = c.beginTransaction(); |
| final ApprovalCategory cat; |
| final ArrayList<ApprovalCategoryValue> vals; |
| |
| cat = new ApprovalCategory(ApprovalCategory.READ, "Read Access"); |
| cat.setPosition((short) -1); |
| cat.setFunctionName(NoOpFunction.NAME); |
| vals = new ArrayList<ApprovalCategoryValue>(); |
| vals.add(value(cat, 1, "Read access")); |
| vals.add(value(cat, -1, "No access")); |
| c.approvalCategories().insert(Collections.singleton(cat), txn); |
| c.approvalCategoryValues().insert(vals, txn); |
| txn.commit(); |
| { |
| final ProjectRight read = |
| new ProjectRight(new ProjectRight.Key(ProjectRight.WILD_PROJECT, cat |
| .getId(), sConfig.anonymousGroupId)); |
| read.setMaxValue((short) 1); |
| read.setMinValue((short) 1); |
| c.projectRights().insert(Collections.singleton(read)); |
| } |
| { |
| final ProjectRight read = |
| new ProjectRight(new ProjectRight.Key(ProjectRight.WILD_PROJECT, cat |
| .getId(), sConfig.adminGroupId)); |
| read.setMaxValue((short) 1); |
| read.setMinValue((short) 1); |
| c.projectRights().insert(Collections.singleton(read)); |
| } |
| } |
| |
| private void initSubmitCategory(final ReviewDb c) throws OrmException { |
| final Transaction txn = c.beginTransaction(); |
| final ApprovalCategory cat; |
| final ArrayList<ApprovalCategoryValue> vals; |
| |
| cat = new ApprovalCategory(ApprovalCategory.SUBMIT, "Submit"); |
| cat.setPosition((short) -1); |
| cat.setFunctionName(SubmitFunction.NAME); |
| vals = new ArrayList<ApprovalCategoryValue>(); |
| vals.add(value(cat, 1, "Submit")); |
| c.approvalCategories().insert(Collections.singleton(cat), txn); |
| c.approvalCategoryValues().insert(vals, txn); |
| txn.commit(); |
| } |
| |
| private void initPushTagCategory(final ReviewDb c) throws OrmException { |
| final Transaction txn = c.beginTransaction(); |
| final ApprovalCategory cat; |
| final ArrayList<ApprovalCategoryValue> vals; |
| |
| cat = new ApprovalCategory(ApprovalCategory.PUSH_TAG, "Push Annotated Tag"); |
| cat.setPosition((short) -1); |
| cat.setFunctionName(NoOpFunction.NAME); |
| vals = new ArrayList<ApprovalCategoryValue>(); |
| vals.add(value(cat, ApprovalCategory.PUSH_TAG_SIGNED, "Create Signed Tag")); |
| vals.add(value(cat, ApprovalCategory.PUSH_TAG_ANNOTATED, |
| "Create Annotated Tag")); |
| vals.add(value(cat, ApprovalCategory.PUSH_TAG_ANY, "Create Any Tag")); |
| c.approvalCategories().insert(Collections.singleton(cat), txn); |
| c.approvalCategoryValues().insert(vals, txn); |
| txn.commit(); |
| } |
| |
| private void initPushUpdateBranchCategory(final ReviewDb c) |
| throws OrmException { |
| final Transaction txn = c.beginTransaction(); |
| final ApprovalCategory cat; |
| final ArrayList<ApprovalCategoryValue> vals; |
| |
| cat = new ApprovalCategory(ApprovalCategory.PUSH_HEAD, "Push Branch"); |
| cat.setPosition((short) -1); |
| cat.setFunctionName(NoOpFunction.NAME); |
| vals = new ArrayList<ApprovalCategoryValue>(); |
| vals.add(value(cat, ApprovalCategory.PUSH_HEAD_UPDATE, "Update Branch")); |
| vals.add(value(cat, ApprovalCategory.PUSH_HEAD_CREATE, "Create Branch")); |
| vals.add(value(cat, ApprovalCategory.PUSH_HEAD_REPLACE, |
| "Force Push Branch; Delete Branch")); |
| c.approvalCategories().insert(Collections.singleton(cat), txn); |
| c.approvalCategoryValues().insert(vals, txn); |
| txn.commit(); |
| } |
| |
| private static ApprovalCategoryValue value(final ApprovalCategory cat, |
| final int value, final String name) { |
| return new ApprovalCategoryValue(new ApprovalCategoryValue.Id(cat.getId(), |
| (short) value), name); |
| } |
| |
| private void loadSystemConfig() throws OrmException { |
| final ReviewDb c = db.open(); |
| try { |
| SchemaVersion sVer; |
| try { |
| sVer = c.schemaVersion().get(new SchemaVersion.Key()); |
| } catch (OrmException e) { |
| // Assume the schema doesn't exist. |
| // |
| sVer = null; |
| } |
| |
| if (sVer == null) { |
| // Assume the schema is empty and populate it. |
| // |
| c.createSchema(); |
| sVer = SchemaVersion.create(); |
| sVer.versionNbr = ReviewDb.VERSION; |
| c.schemaVersion().insert(Collections.singleton(sVer)); |
| |
| initSystemConfig(c); |
| sConfig = c.systemConfig().get(new SystemConfig.Key()); |
| initWildCardProject(c); |
| initReadCategory(c); |
| initVerifiedCategory(c); |
| initCodeReviewCategory(c); |
| initSubmitCategory(c); |
| initPushTagCategory(c); |
| initPushUpdateBranchCategory(c); |
| } |
| |
| if (sVer.versionNbr == 2) { |
| initPushTagCategory(c); |
| initPushUpdateBranchCategory(c); |
| |
| sVer.versionNbr = 3; |
| c.schemaVersion().update(Collections.singleton(sVer)); |
| } |
| |
| if (sVer.versionNbr == ReviewDb.VERSION) { |
| final List<SystemConfig> all = c.systemConfig().all().toList(); |
| switch (all.size()) { |
| case 0: |
| throw new OrmException("system_config table is empty"); |
| case 1: |
| sConfig = all.get(0); |
| break; |
| default: |
| throw new OrmException("system_config must have exactly 1 row;" |
| + " found " + all.size() + " rows instead"); |
| } |
| |
| } else { |
| throw new OrmException("Unsupported schema version " + sVer.versionNbr |
| + "; expected schema version " + ReviewDb.VERSION); |
| } |
| } finally { |
| c.close(); |
| } |
| } |
| |
| private void loadGerritConfig(final ReviewDb db) throws OrmException { |
| final GerritConfig r = new GerritConfig(); |
| r.setCanonicalUrl(getCanonicalURL()); |
| r.setUseContributorAgreements(getGerritConfig().getBoolean("auth", |
| "contributoragreements", false)); |
| r.setGitDaemonUrl(getGerritConfig().getString("gerrit", null, |
| "canonicalgiturl")); |
| r.setUseRepoDownload(getGerritConfig().getBoolean("repo", null, |
| "showdownloadcommand", false)); |
| r.setUseContactInfo(getContactStoreURL() != null); |
| r.setAllowRegisterNewEmail(isOutgoingMailEnabled()); |
| r.setLoginType(getLoginType()); |
| |
| final String gitwebUrl = getGerritConfig().getString("gitweb", null, "url"); |
| if (gitwebUrl != null) { |
| r.setGitwebLink(new GitwebLink(gitwebUrl)); |
| } |
| |
| for (final ApprovalCategory c : db.approvalCategories().all()) { |
| r.add(new ApprovalType(c, db.approvalCategoryValues().byCategory( |
| c.getId()).toList())); |
| } |
| |
| Common.setGerritConfig(r); |
| } |
| |
| public boolean isOutgoingMailEnabled() { |
| return getGerritConfig().getBoolean("sendemail", null, "enable", true); |
| } |
| |
| public SMTPClient createOutgoingMail() throws EmailException { |
| if (!isOutgoingMailEnabled()) { |
| throw new EmailException("Sending email is disabled"); |
| } |
| |
| final RepositoryConfig cfg = getGerritConfig(); |
| String smtpHost = cfg.getString("sendemail", null, "smtpserver"); |
| if (smtpHost == null) { |
| smtpHost = "127.0.0.1"; |
| } |
| int smtpPort = cfg.getInt("sendemail", null, "smtpserverport", 25); |
| |
| String smtpUser = cfg.getString("sendemail", null, "smtpuser"); |
| String smtpPass = cfg.getString("sendemail", null, "smtpuserpass"); |
| |
| final AuthSMTPClient client = new AuthSMTPClient("UTF-8"); |
| client.setAllowRcpt(cfg.getStringList("sendemail", null, "allowrcpt")); |
| try { |
| client.connect(smtpHost, smtpPort); |
| if (!SMTPReply.isPositiveCompletion(client.getReplyCode())) { |
| throw new EmailException("SMTP server rejected connection"); |
| } |
| if (!client.login()) { |
| String e = client.getReplyString(); |
| throw new EmailException("SMTP server rejected login: " + e); |
| } |
| if (smtpUser != null && !client.auth(smtpUser, smtpPass)) { |
| String e = client.getReplyString(); |
| throw new EmailException("SMTP server rejected auth: " + e); |
| } |
| } catch (IOException e) { |
| if (client.isConnected()) { |
| try { |
| client.disconnect(); |
| } catch (IOException e2) { |
| } |
| } |
| throw new EmailException(e.getMessage(), e); |
| } catch (EmailException e) { |
| if (client.isConnected()) { |
| try { |
| client.disconnect(); |
| } catch (IOException e2) { |
| } |
| } |
| throw e; |
| } |
| return client; |
| } |
| |
| private void reconfigureWindowCache() { |
| final WindowCacheConfig c = new WindowCacheConfig(); |
| c.fromConfig(gerritConfigFile); |
| WindowCache.reconfigure(c); |
| } |
| |
| private void reloadSubmitQueue() { |
| WorkQueue.schedule(new Runnable() { |
| public void run() { |
| final HashSet<Branch.NameKey> pending = new HashSet<Branch.NameKey>(); |
| try { |
| final ReviewDb c = db.open(); |
| try { |
| for (final Change change : c.changes().allSubmitted()) { |
| pending.add(change.getDest()); |
| } |
| } finally { |
| c.close(); |
| } |
| } catch (OrmException e) { |
| log.error("Cannot reload MergeQueue", e); |
| } |
| |
| for (final Branch.NameKey branch : pending) { |
| MergeQueue.schedule(branch); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return "Reload Submit Queue"; |
| } |
| }, 15, TimeUnit.SECONDS); |
| } |
| |
| /** Time (in seconds) that user sessions stay "signed in". */ |
| public int getSessionAge() { |
| return sessionAge; |
| } |
| |
| /** Get the signature support used to protect against XSRF attacks. */ |
| public SignedToken getXsrfToken() { |
| return xsrf; |
| } |
| |
| /** Get the signature support used to protect user identity cookies. */ |
| public SignedToken getAccountToken() { |
| return account; |
| } |
| |
| /** Get the signature used for email registration/validation links. */ |
| public SignedToken getEmailRegistrationToken() { |
| return emailReg; |
| } |
| |
| private SystemConfig.LoginType getLoginType() { |
| String type = getGerritConfig().getString("auth", null, "type"); |
| if (type == null) { |
| return SystemConfig.LoginType.OPENID; |
| } |
| for (SystemConfig.LoginType t : SystemConfig.LoginType.values()) { |
| if (type.equalsIgnoreCase(t.name())) { |
| return t; |
| } |
| } |
| throw new IllegalStateException("Unsupported auth.type: " + type); |
| } |
| |
| public String getLoginHttpHeader() { |
| return getGerritConfig().getString("auth", null, "httpheader"); |
| } |
| |
| public String getEmailFormat() { |
| return getGerritConfig().getString("auth", null, "emailformat"); |
| } |
| |
| public String getContactStoreURL() { |
| return getGerritConfig().getString("contactstore", null, "url"); |
| } |
| |
| public String getContactStoreAPPSEC() { |
| return getGerritConfig().getString("contactstore", null, "appsec"); |
| } |
| |
| /** A binary string key to encrypt cookies related to account data. */ |
| public String getAccountCookieKey() { |
| byte[] r = new byte[sConfig.accountPrivateKey.length()]; |
| for (int k = r.length - 1; k >= 0; k--) { |
| r[k] = (byte) sConfig.accountPrivateKey.charAt(k); |
| } |
| r = Base64.decodeBase64(r); |
| final StringBuilder b = new StringBuilder(); |
| for (int i = 0; i < r.length; i++) { |
| b.append((char) r[i]); |
| } |
| return b.toString(); |
| } |
| |
| /** Local filesystem location of header/footer/CSS configuration files. */ |
| public File getSitePath() { |
| return sConfig.sitePath != null ? new File(sConfig.sitePath) : null; |
| } |
| |
| /** Optional canonical URL for this application. */ |
| public String getCanonicalURL() { |
| String u = getGerritConfig().getString("gerrit", null, "canonicalweburl"); |
| if (u != null && !u.endsWith("/")) { |
| u += "/"; |
| } |
| return u; |
| } |
| |
| /** Get the parsed <code>$site_path/gerrit.config</code> file. */ |
| public RepositoryConfig getGerritConfig() { |
| return gerritConfigFile; |
| } |
| |
| /** Get the repositories maintained by this server. */ |
| public RepositoryCache getRepositoryCache() { |
| return repositories; |
| } |
| |
| /** Get all registered caches. */ |
| public Ehcache[] getAllCaches() { |
| final String[] cacheNames = cacheMgr.getCacheNames(); |
| Arrays.sort(cacheNames); |
| final Ehcache[] r = new Ehcache[cacheNames.length]; |
| for (int i = 0; i < cacheNames.length; i++) { |
| r[i] = cacheMgr.getEhcache(cacheNames[i]); |
| } |
| return r; |
| } |
| |
| /** Get any existing cache by name. */ |
| public Cache getCache(final String name) { |
| return cacheMgr.getCache(name); |
| } |
| |
| /** Get the self-populating cache of DiffCacheContent entities. */ |
| public SelfPopulatingCache getDiffCache() { |
| return diffCache; |
| } |
| |
| /** Get the self-populating cache of user SSH keys. */ |
| public SelfPopulatingCache getSshKeysCache() { |
| return sshKeysCache; |
| } |
| |
| /** Get a new identity representing this Gerrit server in Git. */ |
| public PersonIdent newGerritPersonIdent() { |
| String name = getGerritConfig().getString("user", null, "name"); |
| if (name == null) { |
| name = "Gerrit Code Review"; |
| } |
| String email = getGerritConfig().getCommitterEmail(); |
| if (email == null || email.length() == 0) { |
| email = "gerrit@localhost"; |
| } |
| return new PersonIdent(name, email); |
| } |
| |
| public boolean isAllowGoogleAccountUpgrade() { |
| return getGerritConfig().getBoolean("auth", "allowgoogleaccountupgrade", |
| false); |
| } |
| } |