diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java index 85b74bbdcc..4c10ace677 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java @@ -76,6 +76,7 @@ public class ProjectConfig extends VersionedMetaData { private Map accessSections; private List validationErrors; private String prologRules; + private ObjectId rulesId; public static ProjectConfig read(MetaDataUpdate update) throws IOException, ConfigInvalidException { @@ -163,6 +164,14 @@ public class ProjectConfig extends VersionedMetaData { return prologRules; } + /** + * @return the project's rules.pl ObjectId, if present in the branch. + * Null if it doesn't exist. + */ + public ObjectId getRulesId() { + return rulesId; + } + /** * Check all GroupReferences use current group name, repairing stale ones. * @@ -204,6 +213,7 @@ public class ProjectConfig extends VersionedMetaData { Map groupsByName = readGroupList(); prologRules = readUTF8("rules.pl"); + rulesId = getObjectId("rules.pl"); Config rc = readConfig(PROJECT_CONFIG); project = new Project(projectName); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java index 55dd2e183c..655daa3a02 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java @@ -264,6 +264,19 @@ public abstract class VersionedMetaData { } } + protected ObjectId getObjectId(String fileName) throws IOException { + if (revision == null) { + return null; + } + + TreeWalk tw = TreeWalk.forPath(reader, fileName, revision.getTree()); + if (tw != null) { + return tw.getObjectId(0); + } + + return null; + } + protected static void set(Config rc, String section, String subsection, String name, String value) { if (value != null) { diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java index afa1e2f7dc..07bd3324cf 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java @@ -60,6 +60,7 @@ public class ProjectState { private final ProjectConfig config; private final Set localOwners; + private final ClassLoader ruleLoader; /** Last system time the configuration's revision was examined. */ private transient long lastCheckTime; @@ -71,6 +72,7 @@ public class ProjectState { final ProjectControl.AssistedFactory projectControlFactory, final PrologEnvironment.Factory envFactory, final GitRepositoryManager gitMgr, + final RulesCache rulesCache, @Assisted final ProjectConfig config) { this.projectCache = projectCache; this.isAllProjects = config.getProject().getNameKey().equals(allProjectsName); @@ -79,6 +81,11 @@ public class ProjectState { this.gitMgr = gitMgr; this.config = config; this.lastCheckTime = System.currentTimeMillis(); + if (rulesCache != null) { + ruleLoader = rulesCache.getClassLoader(config.getRulesId()); + } else { + ruleLoader = null; + } HashSet groups = new HashSet(); AccessSection all = config.getAccessSection(AccessSection.ALL); @@ -126,7 +133,10 @@ public class ProjectState { /** @return Construct a new PrologEnvironment for the calling thread. */ public PrologEnvironment newPrologEnvironment() throws CompileException { - // TODO Replace this with a per-project ClassLoader to isolate rules. + if (ruleLoader != null) { + return envFactory.create(ruleLoader); + } + PrologEnvironment env = envFactory.create(getClass().getClassLoader()); //consult rules.pl at refs/meta/config branch for custom submit rules diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RulesCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RulesCache.java new file mode 100644 index 0000000000..a8fbce99c3 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RulesCache.java @@ -0,0 +1,121 @@ +// Copyright (C) 2011 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.project; + +import com.google.gerrit.server.config.GerritServerConfig; +import com.google.gerrit.server.config.SitePaths; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.ObjectId; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.HashMap; +import java.util.Map; + +@Singleton +public class RulesCache { + private static final Logger log = LoggerFactory.getLogger(RulesCache.class); + + private final Map classLoaderCache = + new HashMap(); + + private final ReferenceQueue DEAD = new ReferenceQueue(); + + private final File cacheDir; + private final File rulesDir; + + private final class LoaderRef extends WeakReference { + final ObjectId key; + + LoaderRef(ObjectId key, ClassLoader loader) { + super(loader, DEAD); + this.key = key; + } + } + + @Inject + protected RulesCache (@GerritServerConfig Config config, SitePaths site) { + cacheDir = site.resolve(config.getString("cache", null, "directory")); + if (cacheDir != null) { + rulesDir = new File(cacheDir, "rules"); + } else { + rulesDir = null; + } + } + + /** @return URLClassLoader with precompiled rules jar from rules.pl if it exists, + * null otherwise + */ + public synchronized ClassLoader getClassLoader(ObjectId rulesId) { + if (rulesId == null || rulesDir == null) { + return null; + } + + Reference ref = classLoaderCache.get(rulesId); + if (ref != null) { + ClassLoader cl = ref.get(); + if (cl != null) { + return cl; + } + classLoaderCache.remove(rulesId); + ref.enqueue(); + } + + cleanCache(); + + //read jar from (site)/cache/rules + //the included jar file should be in format: + //rules-(rules.pl's sha1).jar + File jarFile = new File(rulesDir, "rules-" + rulesId.getName() + ".jar"); + if (!jarFile.isFile()) { + return null; + } + + ClassLoader defaultLoader = getClass().getClassLoader(); + URL url; + try { + url = jarFile.toURI().toURL(); + } catch (MalformedURLException e) { + log.error("Path to rules jar is broken", e); + return null; + } + + ClassLoader urlLoader = new URLClassLoader(new URL[]{url}, defaultLoader); + + LoaderRef lRef = new LoaderRef(rulesId, urlLoader); + classLoaderCache.put(rulesId, lRef); + return urlLoader; + } + + private void cleanCache() { + Reference ref; + while ((ref = DEAD.poll()) != null) { + ObjectId key = ((LoaderRef) ref).key; + if (classLoaderCache.get(key) == ref) { + classLoaderCache.remove(key); + } + } + } +} \ No newline at end of file diff --git a/gerrit-server/src/main/prolog/gerrit_common.pl b/gerrit-server/src/main/prolog/gerrit_common.pl index ba0336e3c7..e4a99e6e9f 100644 --- a/gerrit-server/src/main/prolog/gerrit_common.pl +++ b/gerrit-server/src/main/prolog/gerrit_common.pl @@ -170,12 +170,12 @@ is_all_ok(_) :- fail. %% locate_submit_rule(RuleName) :- - clause(user:submit_rule(_), _), + '$compiled_predicate'(user, submit_rule, 1), !, RuleName = user:submit_rule . locate_submit_rule(RuleName) :- - '$compiled_predicate'(user, submit_rule, 1), + clause(user:submit_rule(_), _), !, RuleName = user:submit_rule . diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java index bcffa2fee4..e02c6b189b 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java @@ -317,12 +317,13 @@ public class RefControlTest extends TestCase { PrologEnvironment.Factory envFactory = null; GitRepositoryManager mgr = null; ProjectControl.AssistedFactory projectControlFactory = null; + RulesCache rulesCache = null; all.put(local.getProject().getNameKey(), new ProjectState( projectCache, allProjectsName, projectControlFactory, - envFactory, mgr, local)); + envFactory, mgr, rulesCache, local)); all.put(parent.getProject().getNameKey(), new ProjectState( projectCache, allProjectsName, projectControlFactory, - envFactory, mgr, parent)); + envFactory, mgr, rulesCache, parent)); return all.get(local.getProject().getNameKey()); }