Package com.google.gerrit.rules

Source Code of com.google.gerrit.rules.RulesCache$MachineRef

// 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);
    } catch (IOException 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);
    }
  }
}
TOP

Related Classes of com.google.gerrit.rules.RulesCache$MachineRef

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.