summaryrefslogtreecommitdiffstats
path: root/gerrit-server/src/main/java/com/google/gerrit/rules/RulesCache.java
diff options
context:
space:
mode:
Diffstat (limited to 'gerrit-server/src/main/java/com/google/gerrit/rules/RulesCache.java')
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/rules/RulesCache.java227
1 files changed, 227 insertions, 0 deletions
diff --git a/gerrit-server/src/main/java/com/google/gerrit/rules/RulesCache.java b/gerrit-server/src/main/java/com/google/gerrit/rules/RulesCache.java
new file mode 100644
index 0000000000..fd72e0c4b9
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/rules/RulesCache.java
@@ -0,0 +1,227 @@
+// 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.rules;
+
+import static com.googlecode.prolog_cafe.lang.PrologMachineCopy.save;
+
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import com.googlecode.prolog_cafe.compiler.CompileException;
+import com.googlecode.prolog_cafe.lang.BufferingPrologControl;
+import com.googlecode.prolog_cafe.lang.JavaObjectTerm;
+import com.googlecode.prolog_cafe.lang.Prolog;
+import com.googlecode.prolog_cafe.lang.PrologClassLoader;
+import com.googlecode.prolog_cafe.lang.PrologMachineCopy;
+import com.googlecode.prolog_cafe.lang.SymbolTerm;
+
+import org.eclipse.jgit.errors.LargeObjectException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.util.RawParseUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PushbackReader;
+import java.io.StringReader;
+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.EnumSet;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Manages a cache of compiled Prolog rules.
+ * <p>
+ * Rules are loaded from the {@code site_path/cache/rules/rules-SHA1.jar}, where
+ * {@code SHA1} is the SHA1 of the Prolog {@code rules.pl} in a project's
+ * {@link GitRepositoryManager#REF_CONFIG} branch.
+ */
+@Singleton
+public class RulesCache {
+ /** Maximum size of a dynamic Prolog script, in bytes. */
+ private static final int SRC_LIMIT = 128 * 1024;
+
+ /** Default size of the internal Prolog database within each interpreter. */
+ private static final int DB_MAX = 256;
+
+ private static final String[] PACKAGE_LIST = {
+ Prolog.BUILTIN,
+ "gerrit",
+ };
+
+ private final Map<ObjectId, MachineRef> machineCache =
+ new HashMap<ObjectId, MachineRef>();
+
+ private final ReferenceQueue<PrologMachineCopy> dead =
+ new ReferenceQueue<PrologMachineCopy>();
+
+ private static final class MachineRef extends WeakReference<PrologMachineCopy> {
+ final ObjectId key;
+
+ MachineRef(ObjectId key, PrologMachineCopy pcm,
+ ReferenceQueue<PrologMachineCopy> queue) {
+ super(pcm, queue);
+ this.key = key;
+ }
+ }
+
+ private final boolean enableProjectRules;
+ private final File cacheDir;
+ private final File rulesDir;
+ private final GitRepositoryManager gitMgr;
+ private final ClassLoader systemLoader;
+ private final PrologMachineCopy defaultMachine;
+
+ @Inject
+ protected RulesCache(@GerritServerConfig Config config, SitePaths site,
+ GitRepositoryManager gm) {
+ enableProjectRules = config.getBoolean("rules", null, "enable", true);
+ cacheDir = site.resolve(config.getString("cache", null, "directory"));
+ rulesDir = cacheDir != null ? new File(cacheDir, "rules") : null;
+ gitMgr = gm;
+
+ systemLoader = getClass().getClassLoader();
+ defaultMachine = save(newEmptyMachine(systemLoader));
+ }
+
+ /**
+ * Locate a cached Prolog machine state, or create one if not available.
+ *
+ * @return a Prolog machine, after loading the specified rules.
+ * @throws CompileException the machine cannot be created.
+ */
+ public synchronized PrologMachineCopy loadMachine(
+ Project.NameKey project,
+ ObjectId rulesId)
+ throws CompileException {
+ if (!enableProjectRules || project == null || rulesId == null) {
+ return defaultMachine;
+ }
+
+ Reference<? extends PrologMachineCopy> ref = machineCache.get(rulesId);
+ if (ref != null) {
+ PrologMachineCopy pmc = ref.get();
+ if (pmc != null) {
+ return pmc;
+ }
+
+ machineCache.remove(rulesId);
+ ref.enqueue();
+ }
+
+ gc();
+
+ PrologMachineCopy pcm = createMachine(project, rulesId);
+ MachineRef newRef = new MachineRef(rulesId, pcm, dead);
+ machineCache.put(rulesId, newRef);
+ return pcm;
+ }
+
+ private void gc() {
+ Reference<?> ref;
+ while ((ref = dead.poll()) != null) {
+ ObjectId key = ((MachineRef) ref).key;
+ if (machineCache.get(key) == ref) {
+ machineCache.remove(key);
+ }
+ }
+ }
+
+ private PrologMachineCopy createMachine(Project.NameKey project,
+ ObjectId rulesId) throws CompileException {
+ // If the rules are available as a complied JAR on local disk, prefer
+ // that over dynamic consult as the bytecode will be faster.
+ //
+ if (rulesDir != null) {
+ File jarFile = new File(rulesDir, "rules-" + rulesId.getName() + ".jar");
+ if (jarFile.isFile()) {
+ URL[] cp = new URL[] {toURL(jarFile)};
+ return save(newEmptyMachine(new URLClassLoader(cp, systemLoader)));
+ }
+ }
+
+ // Dynamically consult the rules into the machine's internal database.
+ //
+ String rules = read(project, rulesId);
+ BufferingPrologControl ctl = newEmptyMachine(systemLoader);
+ PushbackReader in = new PushbackReader(
+ new StringReader(rules),
+ Prolog.PUSHBACK_SIZE);
+
+ if (!ctl.execute(
+ Prolog.BUILTIN, "consult_stream",
+ SymbolTerm.intern("rules.pl"),
+ new JavaObjectTerm(in))) {
+ throw new CompileException("Cannot consult rules of " + project);
+ }
+ return save(ctl);
+ }
+
+ private String read(Project.NameKey project, ObjectId rulesId)
+ throws CompileException {
+ Repository git;
+ try {
+ git = gitMgr.openRepository(project);
+ } catch (RepositoryNotFoundException e) {
+ throw new CompileException("Cannot open repository " + project, e);
+ }
+ try {
+ ObjectLoader ldr = git.open(rulesId, Constants.OBJ_BLOB);
+ byte[] raw = ldr.getCachedBytes(SRC_LIMIT);
+ return RawParseUtils.decode(raw);
+ } catch (LargeObjectException e) {
+ throw new CompileException("rules of " + project + " are too large", e);
+ } catch (RuntimeException e) {
+ throw new CompileException("Cannot load rules of " + project, e);
+ } catch (IOException e) {
+ throw new CompileException("Cannot load rules of " + project, e);
+ } finally {
+ git.close();
+ }
+ }
+
+ private static BufferingPrologControl newEmptyMachine(ClassLoader cl) {
+ BufferingPrologControl ctl = new BufferingPrologControl();
+ ctl.setMaxArity(PrologEnvironment.MAX_ARITY);
+ ctl.setMaxDatabaseSize(DB_MAX);
+ ctl.setPrologClassLoader(new PrologClassLoader(cl));
+ ctl.setEnabled(EnumSet.allOf(Prolog.Feature.class), false);
+
+ // Bootstrap the interpreter and ensure there is clean state.
+ ctl.initialize(PACKAGE_LIST);
+ return ctl;
+ }
+
+ private static URL toURL(File jarFile) throws CompileException {
+ try {
+ return jarFile.toURI().toURL();
+ } catch (MalformedURLException e) {
+ throw new CompileException("Cannot create URL for " + jarFile, e);
+ }
+ }
+}