blob: e11d6aa7c1ae7176f8a4f76feab22562772c54f0 [file] [log] [blame]
// Copyright (C) 2013 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.config;
import static java.util.stream.Collectors.toList;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
import com.google.gerrit.extensions.api.projects.ConfigValue;
import com.google.gerrit.extensions.api.projects.ProjectConfigEntryType;
import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.registration.Extension;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.project.ProjectState;
import com.google.inject.Inject;
import com.google.inject.ProvisionException;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
@ExtensionPoint
public class ProjectConfigEntry {
private final String displayName;
private final String description;
private final boolean inheritable;
private final String defaultValue;
private final ProjectConfigEntryType type;
private final List<String> permittedValues;
public ProjectConfigEntry(String displayName, String defaultValue) {
this(displayName, defaultValue, false);
}
public ProjectConfigEntry(String displayName, String defaultValue, boolean inheritable) {
this(displayName, defaultValue, inheritable, null);
}
public ProjectConfigEntry(
String displayName, String defaultValue, boolean inheritable, String description) {
this(displayName, defaultValue, ProjectConfigEntryType.STRING, null, inheritable, description);
}
public ProjectConfigEntry(String displayName, int defaultValue) {
this(displayName, defaultValue, false);
}
public ProjectConfigEntry(String displayName, int defaultValue, boolean inheritable) {
this(displayName, defaultValue, inheritable, null);
}
public ProjectConfigEntry(
String displayName, int defaultValue, boolean inheritable, String description) {
this(
displayName,
Integer.toString(defaultValue),
ProjectConfigEntryType.INT,
null,
inheritable,
description);
}
public ProjectConfigEntry(String displayName, long defaultValue) {
this(displayName, defaultValue, false);
}
public ProjectConfigEntry(String displayName, long defaultValue, boolean inheritable) {
this(displayName, defaultValue, inheritable, null);
}
public ProjectConfigEntry(
String displayName, long defaultValue, boolean inheritable, String description) {
this(
displayName,
Long.toString(defaultValue),
ProjectConfigEntryType.LONG,
null,
inheritable,
description);
}
// For inheritable boolean use 'LIST' type with InheritableBoolean
public ProjectConfigEntry(String displayName, boolean defaultValue) {
this(displayName, defaultValue, null);
}
// For inheritable boolean use 'LIST' type with InheritableBoolean
public ProjectConfigEntry(String displayName, boolean defaultValue, String description) {
this(
displayName,
Boolean.toString(defaultValue),
ProjectConfigEntryType.BOOLEAN,
null,
false,
description);
}
public ProjectConfigEntry(String displayName, String defaultValue, List<String> permittedValues) {
this(displayName, defaultValue, permittedValues, false);
}
public ProjectConfigEntry(
String displayName, String defaultValue, List<String> permittedValues, boolean inheritable) {
this(displayName, defaultValue, permittedValues, inheritable, null);
}
public ProjectConfigEntry(
String displayName,
String defaultValue,
List<String> permittedValues,
boolean inheritable,
String description) {
this(
displayName,
defaultValue,
ProjectConfigEntryType.LIST,
permittedValues,
inheritable,
description);
}
public <T extends Enum<?>> ProjectConfigEntry(
String displayName, T defaultValue, Class<T> permittedValues) {
this(displayName, defaultValue, permittedValues, false);
}
public <T extends Enum<?>> ProjectConfigEntry(
String displayName, T defaultValue, Class<T> permittedValues, boolean inheritable) {
this(displayName, defaultValue, permittedValues, inheritable, null);
}
public <T extends Enum<?>> ProjectConfigEntry(
String displayName,
T defaultValue,
Class<T> permittedValues,
boolean inheritable,
String description) {
this(
displayName,
defaultValue.name(),
ProjectConfigEntryType.LIST,
Arrays.stream(permittedValues.getEnumConstants()).map(Enum::name).collect(toList()),
inheritable,
description);
}
public ProjectConfigEntry(
String displayName,
String defaultValue,
ProjectConfigEntryType type,
List<String> permittedValues,
boolean inheritable,
String description) {
this.displayName = displayName;
this.defaultValue = defaultValue;
this.type = type;
this.permittedValues = permittedValues;
this.inheritable = inheritable;
this.description = description;
if (type == ProjectConfigEntryType.ARRAY && inheritable) {
throw new ProvisionException("ARRAY doesn't support inheritable values");
}
}
public String getDisplayName() {
return displayName;
}
public String getDescription() {
return description;
}
public boolean isInheritable() {
return inheritable;
}
public String getDefaultValue() {
return defaultValue;
}
public ProjectConfigEntryType getType() {
return type;
}
public List<String> getPermittedValues() {
return permittedValues;
}
/**
* Returns whether the project is editable
*
* @param project project state.
*/
public boolean isEditable(ProjectState project) {
return true;
}
/**
* Returns any warning associated with the project
*
* @param project project state.
*/
public String getWarning(ProjectState project) {
return null;
}
/**
* Called before the project config is updated. To modify the value before the project config is
* updated, override this method and return the modified value. Default implementation returns the
* same value.
*
* @param configValue the original configValue that was entered.
* @return the modified configValue.
*/
public ConfigValue preUpdate(ConfigValue configValue) {
return configValue;
}
/**
* Called after reading the project config value. To modify the value before returning it to the
* client, override this method and return the modified value. Default implementation returns the
* same value.
*
* @param project the project.
* @param value the actual value of the config entry (computed out of the configured value, the
* inherited value and the default value).
* @return the modified value.
*/
public String onRead(ProjectState project, String value) {
return value;
}
/**
* Called after reading the project config value of type ARRAY. To modify the values before
* returning it to the client, override this method and return the modified values. Default
* implementation returns the same values.
*
* @param project the project.
* @param values the actual values of the config entry (computed out of the configured value, the
* inherited value and the default value).
* @return the modified values.
*/
public List<String> onRead(ProjectState project, List<String> values) {
return values;
}
/**
* Called after a project config is updated.
*
* @param project project name.
* @param oldValue old entry value.
* @param newValue new entry value.
*/
public void onUpdate(Project.NameKey project, String oldValue, String newValue) {}
/**
* Called after a project config is updated.
*
* @param project project name.
* @param oldValue old entry value.
* @param newValue new entry value.
*/
public void onUpdate(Project.NameKey project, Boolean oldValue, Boolean newValue) {}
/**
* Called after a project config is updated.
*
* @param project project name.
* @param oldValue old entry value.
* @param newValue new entry value.
*/
public void onUpdate(Project.NameKey project, Integer oldValue, Integer newValue) {}
/**
* Called after a project config is updated.
*
* @param project project name.
* @param oldValue old entry value.
* @param newValue new entry value.
*/
public void onUpdate(Project.NameKey project, Long oldValue, Long newValue) {}
public static class UpdateChecker implements GitReferenceUpdatedListener {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final GitRepositoryManager repoManager;
private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
private final ProjectConfig.Factory projectConfigFactory;
@Inject
UpdateChecker(
GitRepositoryManager repoManager,
DynamicMap<ProjectConfigEntry> pluginConfigEntries,
ProjectConfig.Factory projectConfigFactory) {
this.repoManager = repoManager;
this.pluginConfigEntries = pluginConfigEntries;
this.projectConfigFactory = projectConfigFactory;
}
@Override
public void onGitReferenceUpdated(Event event) {
Project.NameKey p = Project.nameKey(event.getProjectName());
if (!event.getRefName().equals(RefNames.REFS_CONFIG)) {
return;
}
try {
ProjectConfig oldCfg = parseConfig(p, event.getOldObjectId());
ProjectConfig newCfg = parseConfig(p, event.getNewObjectId());
if (oldCfg != null && newCfg != null) {
for (Extension<ProjectConfigEntry> e : pluginConfigEntries) {
ProjectConfigEntry configEntry = e.getProvider().get();
String newValue = getValue(newCfg, e);
String oldValue = getValue(oldCfg, e);
if ((newValue == null && oldValue == null)
|| (newValue != null && newValue.equals(oldValue))) {
return;
}
switch (configEntry.getType()) {
case BOOLEAN:
configEntry.onUpdate(p, toBoolean(oldValue), toBoolean(newValue));
break;
case INT:
configEntry.onUpdate(p, toInt(oldValue), toInt(newValue));
break;
case LONG:
configEntry.onUpdate(p, toLong(oldValue), toLong(newValue));
break;
case LIST:
case STRING:
case ARRAY:
default:
configEntry.onUpdate(p, oldValue, newValue);
}
}
}
} catch (IOException | ConfigInvalidException e) {
logger.atSevere().withCause(e).log(
"Failed to check if plugin config of project %s was updated.", p.get());
}
}
@Nullable
private ProjectConfig parseConfig(Project.NameKey p, String idStr)
throws IOException, ConfigInvalidException, RepositoryNotFoundException {
ObjectId id = ObjectId.fromString(idStr);
if (ObjectId.zeroId().equals(id)) {
return null;
}
try (Repository repo = repoManager.openRepository(p)) {
ProjectConfig pc = projectConfigFactory.create(p);
pc.load(repo, id);
return pc;
}
}
private static String getValue(ProjectConfig cfg, Extension<ProjectConfigEntry> e) {
String value = cfg.getPluginConfig(e.getPluginName()).getString(e.getExportName());
if (value == null) {
value = e.getProvider().get().getDefaultValue();
}
return value;
}
}
@Nullable
private static Boolean toBoolean(String value) {
return value != null ? Boolean.parseBoolean(value) : null;
}
@Nullable
private static Integer toInt(String value) {
return value != null ? Integer.parseInt(value) : null;
}
@Nullable
private static Long toLong(String value) {
return value != null ? Long.parseLong(value) : null;
}
}