Refactor how permissions are matched by ProjectControl, RefControl

AccessSections are now matched onto references using pre-compiled
SectionMatcher objects. These matchers are built in the ProjectState
on demand, and cached until the ProjectState itself is discarded
from memory. This saves the Pattern.compile() costs, as well as some
basic conditionals to determine which type of reference pattern the
section uses, providing a small speed up to access rule evaluation.

ProjectControl and RefControl now stores all permissions that belong
to the project or reference, rather than only the ones relevant for
their CurrentUser. This allows the control objects to provide cached
data for other users, such as when ChangeControl needs to build a
different copy of itself for each reviewer listed on the change.

ProjectControl caches RefControls it builds, making it easier for
callers like ReceiveCommits or VisibleRefFilter to deal with a
lot of lookups for the same common reference name within a single
project access request. This comes at a cost of memory, but should
be an improvement in response time.

Project ownership checks are now handled by ProjectState, relying
on the cached localOwners data instead of looking at the OWNER
permission on "refs/*". The cached localOwners is already built up
from the "refs/*" data during ProjectState's constructor, so doing
it dynamically via RefControl inside of ProjectControl was really
quite wasteful.

Change-Id: Iaf12bab55d41217363cc05ba024f452d03bc21df
This commit is contained in:
Shawn O. Pearce
2011-06-22 17:07:37 -07:00
parent 106796c589
commit bee0aeafa1
7 changed files with 685 additions and 396 deletions

View File

@@ -14,6 +14,7 @@
package com.google.gerrit.server.project;
import com.google.gerrit.common.CollectionsUtil;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.Permission;
@@ -66,6 +67,9 @@ public class ProjectState {
/** 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;
@@ -148,58 +152,62 @@ public class ProjectState {
if (pmc == null) {
pmc = rulesCache.loadMachine(
getProject().getNameKey(),
getConfig().getRulesId());
config.getRulesId());
rulesMachine = pmc;
}
return envFactory.create(pmc);
}
public Project getProject() {
return getConfig().getProject();
return config.getProject();
}
public ProjectConfig getConfig() {
return config;
}
/** Get the rights that pertain only to this project. */
public Collection<AccessSection> getLocalAccessSections() {
return getConfig().getAccessSections();
/** 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) {
SectionMatcher matcher = SectionMatcher.wrap(section);
if (matcher != null) {
sm.add(matcher);
}
}
localAccessSections = sm;
}
return sm;
}
/** Get the rights this project inherits. */
public Collection<AccessSection> getInheritedAccessSections() {
/**
* 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 Collections.emptyList();
return getLocalAccessSections();
}
List<AccessSection> inherited = new ArrayList<AccessSection>();
List<SectionMatcher> all = new ArrayList<SectionMatcher>();
Set<Project.NameKey> seen = new HashSet<Project.NameKey>();
Project.NameKey parent = getProject().getParent();
seen.add(getProject().getNameKey());
while (parent != null && seen.add(parent)) {
ProjectState s = projectCache.get(parent);
if (s != null) {
inherited.addAll(s.getLocalAccessSections());
parent = s.getProject().getParent();
} else {
ProjectState s = this;
do {
all.addAll(s.getLocalAccessSections());
Project.NameKey parent = s.getProject().getParent();
if (parent == null || !seen.add(parent)) {
break;
}
}
// The root of the tree is the special "All-Projects" case.
if (parent == null) {
inherited.addAll(projectCache.getAllProjects().getLocalAccessSections());
}
return inherited;
}
/** Get both local and inherited access sections. */
public Collection<AccessSection> getAllAccessSections() {
List<AccessSection> all = new ArrayList<AccessSection>();
all.addAll(getLocalAccessSections());
all.addAll(getInheritedAccessSections());
s = projectCache.get(parent);
} while (s != null);
all.addAll(projectCache.getAllProjects().getLocalAccessSections());
return all;
}
@@ -224,30 +232,26 @@ public class ProjectState {
}
/**
* @return all {@link AccountGroup}'s that are allowed to administrate the
* complete project. This includes all groups to which the owner
* privilege for 'refs/*' is assigned for this project (the local
* owners) and all groups to which the owner privilege for 'refs/*' is
* assigned for one of the parent projects (the inherited owners).
* @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..
*/
public Set<AccountGroup.UUID> getAllOwners() {
HashSet<AccountGroup.UUID> owners = new HashSet<AccountGroup.UUID>();
owners.addAll(localOwners);
boolean isOwner(Set<AccountGroup.UUID> groups) {
Set<Project.NameKey> seen = new HashSet<Project.NameKey>();
Project.NameKey parent = getProject().getParent();
seen.add(getProject().getNameKey());
while (parent != null && seen.add(parent)) {
ProjectState s = projectCache.get(parent);
if (s != null) {
owners.addAll(s.localOwners);
parent = s.getProject().getParent();
} else {
ProjectState s = this;
do {
if (CollectionsUtil.isAnyIncludedIn(s.localOwners, groups)) {
return true;
}
Project.NameKey parent = s.getProject().getParent();
if (parent == null || !seen.add(parent)) {
break;
}
}
return Collections.unmodifiableSet(owners);
s = projectCache.get(parent);
} while (s != null);
return false;
}
public ProjectControl controlFor(final CurrentUser user) {