blob: e06c9480647b0000d1f6d130a753989084ee912d [file] [log] [blame]
// 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.project;
import com.google.common.collect.Lists;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.rules.PrologEnvironment;
import com.google.gerrit.rules.RulesCache;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.CapabilityCollection;
import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import com.googlecode.prolog_cafe.compiler.CompileException;
import com.googlecode.prolog_cafe.lang.PrologMachineCopy;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/** Cached information on a project. */
public class ProjectState {
public interface Factory {
ProjectState create(ProjectConfig config);
}
private final boolean isAllProjects;
private final AllProjectsName allProjectsName;
private final ProjectCache projectCache;
private final ProjectControl.AssistedFactory projectControlFactory;
private final PrologEnvironment.Factory envFactory;
private final GitRepositoryManager gitMgr;
private final RulesCache rulesCache;
private final ProjectConfig config;
private final Set<AccountGroup.UUID> localOwners;
/** Prolog rule state. */
private volatile PrologMachineCopy rulesMachine;
/** Last system time the configuration's revision was examined. */
private volatile long lastCheckTime;
/** Local access sections, wrapped in SectionMatchers for faster evaluation. */
private volatile List<SectionMatcher> localAccessSections;
/** If this is all projects, the capabilities used by the server. */
private final CapabilityCollection capabilities;
@Inject
protected ProjectState(
final ProjectCache projectCache,
final AllProjectsName allProjectsName,
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);
this.allProjectsName = allProjectsName;
this.projectControlFactory = projectControlFactory;
this.envFactory = envFactory;
this.gitMgr = gitMgr;
this.rulesCache = rulesCache;
this.config = config;
this.capabilities = isAllProjects
? new CapabilityCollection(config.getAccessSection(AccessSection.GLOBAL_CAPABILITIES))
: null;
if (isAllProjects && !Permission.canBeOnAllProjects(AccessSection.ALL, Permission.OWNER)) {
localOwners = Collections.emptySet();
} else {
HashSet<AccountGroup.UUID> groups = new HashSet<AccountGroup.UUID>();
AccessSection all = config.getAccessSection(AccessSection.ALL);
if (all != null) {
Permission owner = all.getPermission(Permission.OWNER);
if (owner != null) {
for (PermissionRule rule : owner.getRules()) {
GroupReference ref = rule.getGroup();
if (ref.getUUID() != null) {
groups.add(ref.getUUID());
}
}
}
}
localOwners = Collections.unmodifiableSet(groups);
}
}
boolean needsRefresh(long generation) {
if (generation <= 0) {
return isRevisionOutOfDate();
}
if (lastCheckTime != generation) {
lastCheckTime = generation;
return isRevisionOutOfDate();
}
return false;
}
private boolean isRevisionOutOfDate() {
try {
Repository git = gitMgr.openRepository(getProject().getNameKey());
try {
Ref ref = git.getRef(GitRepositoryManager.REF_CONFIG);
if (ref == null || ref.getObjectId() == null) {
return true;
}
return !ref.getObjectId().equals(config.getRevision());
} finally {
git.close();
}
} catch (IOException gone) {
return true;
}
}
/**
* @return cached computation of all global capabilities. This should only be
* invoked on the state from {@link ProjectCache#getAllProjects()}.
* Null on any other project.
*/
public CapabilityCollection getCapabilityCollection() {
return capabilities;
}
/** @return Construct a new PrologEnvironment for the calling thread. */
public PrologEnvironment newPrologEnvironment() throws CompileException {
PrologMachineCopy pmc = rulesMachine;
if (pmc == null) {
pmc = rulesCache.loadMachine(
getProject().getNameKey(),
config.getRulesId());
rulesMachine = pmc;
}
return envFactory.create(pmc);
}
public Project getProject() {
return config.getProject();
}
public ProjectConfig getConfig() {
return config;
}
/** Get the sections that pertain only to this project. */
private List<SectionMatcher> getLocalAccessSections() {
List<SectionMatcher> sm = localAccessSections;
if (sm == null) {
Collection<AccessSection> fromConfig = config.getAccessSections();
sm = new ArrayList<SectionMatcher>(fromConfig.size());
for (AccessSection section : fromConfig) {
if (isAllProjects) {
List<Permission> copy =
Lists.newArrayListWithCapacity(section.getPermissions().size());
for (Permission p : section.getPermissions()) {
if (Permission.canBeOnAllProjects(section.getName(), p.getName())) {
copy.add(p);
}
}
section = new AccessSection(section.getName());
section.setPermissions(copy);
}
SectionMatcher matcher = SectionMatcher.wrap(section);
if (matcher != null) {
sm.add(matcher);
}
}
localAccessSections = sm;
}
return sm;
}
/**
* Obtain all local and inherited sections. This collection is looked up
* dynamically and is not cached. Callers should try to cache this result
* per-request as much as possible.
*/
List<SectionMatcher> getAllSections() {
if (isAllProjects) {
return getLocalAccessSections();
}
List<SectionMatcher> all = new ArrayList<SectionMatcher>();
Set<Project.NameKey> seen = new HashSet<Project.NameKey>();
ProjectState allProjects = projectCache.getAllProjects();
seen.add(getProject().getNameKey());
ProjectState s = this;
do {
all.addAll(s.getLocalAccessSections());
Project.NameKey parent = s.getProject().getParent();
if (parent == null || !seen.add(parent)) {
break;
}
s = projectCache.get(parent);
} while (s != null);
if (seen.add(allProjects.getProject().getNameKey())) {
all.addAll(allProjects.getLocalAccessSections());
}
return all;
}
/**
* @return all {@link AccountGroup}'s to which the owner privilege for
* 'refs/*' is assigned for this project (the local owners), if there
* are no local owners the local owners of the nearest parent project
* that has local owners are returned
*/
public Set<AccountGroup.UUID> getOwners() {
Project.NameKey parentName = getProject().getParent();
if (!localOwners.isEmpty() || parentName == null || isAllProjects) {
return localOwners;
}
ProjectState parent = projectCache.get(parentName);
if (parent != null) {
return parent.getOwners();
}
return Collections.emptySet();
}
/**
* @return true if any of the groups listed in {@code groups} was declared to
* be an owner of this project, or one of its parent projects..
*/
boolean isOwner(GroupMembership groups) {
Set<Project.NameKey> seen = new HashSet<Project.NameKey>();
seen.add(getProject().getNameKey());
ProjectState s = this;
do {
if (groups.containsAnyOf(s.localOwners)) {
return true;
}
Project.NameKey parent = s.getProject().getParent();
if (parent == null || !seen.add(parent)) {
break;
}
s = projectCache.get(parent);
} while (s != null);
return false;
}
public ProjectControl controlFor(final CurrentUser user) {
return projectControlFactory.create(user, this);
}
/**
* @return ProjectState of project's parent. If the project does not have a
* parent, return state of the top level project, All-Projects. If
* this project is All-Projects, return null.
*/
public ProjectState getParentState() {
if (isAllProjects) {
return null;
}
return projectCache.get(getProject().getParent(allProjectsName));
}
public boolean isAllProjects() {
return isAllProjects;
}
}