diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt index 2b07cf76b1..c27811afd8 100644 --- a/Documentation/dev-plugins.txt +++ b/Documentation/dev-plugins.txt @@ -710,6 +710,10 @@ class Module extends AbstractModule { } ---- +By overwriting the `onUpdate` method of `ProjectConfigEntry` plugins +can be notified when this configuration parameter is updated on a +project. + [[project-specific-configuration]] == Project Specific Configuration in own config file diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java index 290a2234c7..c9eabe8c05 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java @@ -260,6 +260,8 @@ public class GerritGlobalModule extends FactoryModule { DynamicSet.setOf(binder(), HeadUpdatedListener.class); DynamicSet.bind(binder(), GitReferenceUpdatedListener.class).to(ChangeCache.class); DynamicSet.bind(binder(), GitReferenceUpdatedListener.class).to(MergeabilityChecker.class); + DynamicSet.bind(binder(), GitReferenceUpdatedListener.class) + .to(ProjectConfigEntry.UpdateChecker.class); DynamicSet.setOf(binder(), ChangeListener.class); DynamicSet.setOf(binder(), CommitValidationListener.class); DynamicSet.setOf(binder(), MergeValidationListener.class); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java index c7cd48efdc..c8df7c0a78 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java @@ -17,8 +17,23 @@ package com.google.gerrit.server.config; import com.google.common.base.Function; import com.google.common.collect.Lists; import com.google.gerrit.extensions.annotations.ExtensionPoint; +import com.google.gerrit.extensions.events.GitReferenceUpdatedListener; +import com.google.gerrit.extensions.registration.DynamicMap; +import com.google.gerrit.extensions.registration.DynamicMap.Entry; +import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.reviewdb.client.RefNames; +import com.google.gerrit.server.git.MetaDataUpdate; +import com.google.gerrit.server.git.ProjectConfig; import com.google.gerrit.server.project.ProjectState; +import com.google.inject.Inject; +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.eclipse.jgit.lib.ObjectId; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; import java.util.Arrays; import java.util.List; @@ -126,4 +141,103 @@ public class ProjectConfigEntry { public boolean isEditable(ProjectState project) { return true; } + + public void onUpdate(Project.NameKey project, String oldValue, String newValue) { + } + + public void onUpdate(Project.NameKey project, Boolean oldValue, Boolean newValue) { + } + + public void onUpdate(Project.NameKey project, Integer oldValue, Integer newValue) { + } + + public void onUpdate(Project.NameKey project, Long oldValue, Long newValue) { + } + + public static class UpdateChecker implements GitReferenceUpdatedListener { + private static final Logger log = LoggerFactory.getLogger(UpdateChecker.class); + + private final MetaDataUpdate.Server metaDataUpdateFactory; + private final DynamicMap pluginConfigEntries; + + @Inject + UpdateChecker(MetaDataUpdate.Server metaDataUpdateFactory, + DynamicMap pluginConfigEntries) { + this.metaDataUpdateFactory = metaDataUpdateFactory; + this.pluginConfigEntries = pluginConfigEntries; + } + + @Override + public void onGitReferenceUpdated(Event event) { + Project.NameKey p = new 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 (Entry 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: + default: + configEntry.onUpdate(p, oldValue, newValue); + } + } + } + } catch (IOException | ConfigInvalidException e) { + log.error(String.format( + "Failed to check if plugin config of project %s was updated.", + p.get()), e); + } + } + + private ProjectConfig parseConfig(Project.NameKey p, String idStr) + throws IOException, ConfigInvalidException, RepositoryNotFoundException { + ObjectId id = ObjectId.fromString(idStr); + if (ObjectId.zeroId().equals(id)) { + return null; + } + return ProjectConfig.read(metaDataUpdateFactory.create(p), id); + } + + private static String getValue(ProjectConfig cfg, Entry e) { + String value = cfg.getPluginConfig(e.getPluginName()).getString(e.getExportName()); + if (value == null) { + value = e.getProvider().get().getDefaultValue(); + } + return value; + } + } + + private static Boolean toBoolean(String value) { + return value != null ? Boolean.parseBoolean(value) : null; + } + + private static int toInt(String value) { + return value != null ? Integer.parseInt(value) : null; + } + + private static long toLong(String value) { + return value != null ? Long.parseLong(value) : null; + } }