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:
@@ -0,0 +1,154 @@
|
||||
// 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 static com.google.gerrit.server.project.RefControl.isRE;
|
||||
|
||||
import com.google.gerrit.common.data.AccessSection;
|
||||
import com.google.gerrit.common.data.ParamertizedString;
|
||||
|
||||
import dk.brics.automaton.Automaton;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Matches an AccessSection against a reference name.
|
||||
* <p>
|
||||
* These matchers are "compiled" versions of the AccessSection name, supporting
|
||||
* faster selection of which sections are relevant to any given input reference.
|
||||
*/
|
||||
abstract class SectionMatcher {
|
||||
static SectionMatcher wrap(AccessSection section) {
|
||||
String ref = section.getName();
|
||||
if (AccessSection.isAccessSection(ref)) {
|
||||
return wrap(ref, section);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static SectionMatcher wrap(String pattern, AccessSection section) {
|
||||
if (pattern.contains("${")) {
|
||||
return new ExpandParameters(pattern, section);
|
||||
|
||||
} else if (isRE(pattern)) {
|
||||
return new Regexp(pattern, section);
|
||||
|
||||
} else if (pattern.endsWith("/*")) {
|
||||
return new Prefix(pattern.substring(0, pattern.length() - 1), section);
|
||||
|
||||
} else {
|
||||
return new Exact(pattern, section);
|
||||
}
|
||||
}
|
||||
|
||||
final AccessSection section;
|
||||
|
||||
SectionMatcher(AccessSection section) {
|
||||
this.section = section;
|
||||
}
|
||||
|
||||
abstract boolean match(String ref, String username);
|
||||
|
||||
private static class Exact extends SectionMatcher {
|
||||
private final String expect;
|
||||
|
||||
Exact(String name, AccessSection section) {
|
||||
super(section);
|
||||
expect = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean match(String ref, String username) {
|
||||
return expect.equals(ref);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Prefix extends SectionMatcher {
|
||||
private final String prefix;
|
||||
|
||||
Prefix(String pfx, AccessSection section) {
|
||||
super(section);
|
||||
prefix = pfx;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean match(String ref, String username) {
|
||||
return ref.startsWith(prefix);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Regexp extends SectionMatcher {
|
||||
private final Pattern pattern;
|
||||
|
||||
Regexp(String re, AccessSection section) {
|
||||
super(section);
|
||||
pattern = Pattern.compile(re);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean match(String ref, String username) {
|
||||
return pattern.matcher(ref).matches();
|
||||
}
|
||||
}
|
||||
|
||||
static class ExpandParameters extends SectionMatcher {
|
||||
private final ParamertizedString template;
|
||||
private final String prefix;
|
||||
|
||||
ExpandParameters(String pattern, AccessSection section) {
|
||||
super(section);
|
||||
template = new ParamertizedString(pattern);
|
||||
|
||||
if (isRE(pattern)) {
|
||||
// Replace ${username} with ":USERNAME:" as : is not legal
|
||||
// in a reference and the string :USERNAME: is not likely to
|
||||
// be a valid part of the regex. This later allows the pattern
|
||||
// prefix to be clipped, saving time on evaluation.
|
||||
Automaton am = RefControl.toRegExp(
|
||||
template.replace(Collections.singletonMap("username", ":USERNAME:")))
|
||||
.toAutomaton();
|
||||
String rePrefix = am.getCommonPrefix();
|
||||
prefix = rePrefix.substring(0, rePrefix.indexOf(":USERNAME:"));
|
||||
} else {
|
||||
prefix = pattern.substring(0, pattern.indexOf("${"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean match(String ref, String username) {
|
||||
if (!ref.startsWith(prefix) || username == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String u;
|
||||
if (isRE(template.getPattern())) {
|
||||
u = username.replace(".", "\\.");
|
||||
} else {
|
||||
u = username;
|
||||
}
|
||||
|
||||
SectionMatcher next = wrap(
|
||||
template.replace(Collections.singletonMap("username", u)),
|
||||
section);
|
||||
return next != null ? next.match(ref, username) : false;
|
||||
}
|
||||
|
||||
boolean matchPrefix(String ref) {
|
||||
return ref.startsWith(prefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user