Line data Source code
1 : // Copyright (C) 2018 The Android Open Source Project
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 : package com.google.gerrit.server.config;
15 :
16 : import com.google.common.collect.ArrayListMultimap;
17 : import com.google.common.collect.ImmutableMultimap;
18 : import com.google.common.collect.Multimap;
19 : import java.util.Collections;
20 : import java.util.LinkedHashSet;
21 : import java.util.Objects;
22 : import java.util.Set;
23 : import org.apache.commons.lang3.StringUtils;
24 : import org.eclipse.jgit.lib.Config;
25 :
26 : /**
27 : * This event is produced by {@link GerritServerConfigReloader} and forwarded to callers
28 : * implementing {@link GerritConfigListener}.
29 : *
30 : * <p>The event intends to:
31 : *
32 : * <p>1. Help the callers figure out if any action should be taken, depending on which entries are
33 : * updated in gerrit.config.
34 : *
35 : * <p>2. Provide the callers with a mechanism to accept/reject the entries of interest: {@link
36 : * #accept(Set)}, {@link #accept(String)}, {@link #reject(Set)} (+ various overloaded versions of
37 : * these)
38 : */
39 : public class ConfigUpdatedEvent {
40 1 : public static final ImmutableMultimap<UpdateResult, ConfigUpdateEntry> NO_UPDATES =
41 1 : new ImmutableMultimap.Builder<UpdateResult, ConfigUpdateEntry>().build();
42 : private final Config oldConfig;
43 : private final Config newConfig;
44 :
45 1 : public ConfigUpdatedEvent(Config oldConfig, Config newConfig) {
46 1 : this.oldConfig = oldConfig;
47 1 : this.newConfig = newConfig;
48 1 : }
49 :
50 : public Config getOldConfig() {
51 0 : return this.oldConfig;
52 : }
53 :
54 : public Config getNewConfig() {
55 0 : return this.newConfig;
56 : }
57 :
58 : private String getString(ConfigKey key, Config config) {
59 0 : return config.getString(key.section(), key.subsection(), key.name());
60 : }
61 :
62 : public Multimap<UpdateResult, ConfigUpdateEntry> accept(ConfigKey entry) {
63 0 : return accept(Collections.singleton(entry));
64 : }
65 :
66 : public Multimap<UpdateResult, ConfigUpdateEntry> accept(Set<ConfigKey> entries) {
67 0 : return createUpdate(entries, UpdateResult.APPLIED);
68 : }
69 :
70 : public Multimap<UpdateResult, ConfigUpdateEntry> accept(String section) {
71 0 : Set<ConfigKey> entries = getEntriesFromSection(oldConfig, section);
72 0 : entries.addAll(getEntriesFromSection(newConfig, section));
73 0 : return createUpdate(entries, UpdateResult.APPLIED);
74 : }
75 :
76 : public Multimap<UpdateResult, ConfigUpdateEntry> reject(ConfigKey entry) {
77 0 : return reject(Collections.singleton(entry));
78 : }
79 :
80 : public Multimap<UpdateResult, ConfigUpdateEntry> reject(Set<ConfigKey> entries) {
81 0 : return createUpdate(entries, UpdateResult.REJECTED);
82 : }
83 :
84 : private static Set<ConfigKey> getEntriesFromSection(Config config, String section) {
85 1 : Set<ConfigKey> res = new LinkedHashSet<>();
86 1 : for (String name : config.getNames(section, true)) {
87 0 : res.add(ConfigKey.create(section, name));
88 0 : }
89 1 : for (String sub : config.getSubsections(section)) {
90 0 : for (String name : config.getNames(section, sub, true)) {
91 0 : res.add(ConfigKey.create(section, sub, name));
92 0 : }
93 0 : }
94 1 : return res;
95 : }
96 :
97 : private Multimap<UpdateResult, ConfigUpdateEntry> createUpdate(
98 : Set<ConfigKey> entries, UpdateResult updateResult) {
99 0 : Multimap<UpdateResult, ConfigUpdateEntry> updates = ArrayListMultimap.create();
100 0 : entries.stream()
101 0 : .filter(this::isValueUpdated)
102 0 : .map(e -> new ConfigUpdateEntry(e, getString(e, oldConfig), getString(e, newConfig)))
103 0 : .forEach(e -> updates.put(updateResult, e));
104 0 : return updates;
105 : }
106 :
107 : public boolean isSectionUpdated(String section) {
108 1 : Set<ConfigKey> entries = getEntriesFromSection(oldConfig, section);
109 1 : entries.addAll(getEntriesFromSection(newConfig, section));
110 1 : return isEntriesUpdated(entries);
111 : }
112 :
113 : public boolean isValueUpdated(String section, String subsection, String name) {
114 1 : return !Objects.equals(
115 1 : oldConfig.getString(section, subsection, name),
116 1 : newConfig.getString(section, subsection, name));
117 : }
118 :
119 : public boolean isValueUpdated(ConfigKey key) {
120 1 : return isValueUpdated(key.section(), key.subsection(), key.name());
121 : }
122 :
123 : public boolean isValueUpdated(String section, String name) {
124 0 : return isValueUpdated(section, null, name);
125 : }
126 :
127 : public boolean isEntriesUpdated(Set<ConfigKey> entries) {
128 1 : for (ConfigKey entry : entries) {
129 1 : if (isValueUpdated(entry.section(), entry.subsection(), entry.name())) {
130 0 : return true;
131 : }
132 1 : }
133 1 : return false;
134 : }
135 :
136 0 : public enum UpdateResult {
137 0 : APPLIED,
138 0 : REJECTED;
139 :
140 : @Override
141 : public String toString() {
142 0 : return StringUtils.capitalize(name().toLowerCase());
143 : }
144 : }
145 :
146 0 : public enum ConfigEntryType {
147 0 : ADDED,
148 0 : REMOVED,
149 0 : MODIFIED,
150 0 : UNMODIFIED
151 : }
152 :
153 : public static class ConfigUpdateEntry {
154 : public final ConfigKey key;
155 : public final String oldVal;
156 : public final String newVal;
157 :
158 0 : public ConfigUpdateEntry(ConfigKey key, String oldVal, String newVal) {
159 0 : this.key = key;
160 0 : this.oldVal = oldVal;
161 0 : this.newVal = newVal;
162 0 : }
163 :
164 : /** Note: The toString() is used to format the output from @see ReloadConfig. */
165 : @Override
166 : public String toString() {
167 0 : switch (getUpdateType()) {
168 : case ADDED:
169 0 : return String.format("+ %s = %s", key, newVal);
170 : case MODIFIED:
171 0 : return String.format("* %s = [%s => %s]", key, oldVal, newVal);
172 : case REMOVED:
173 0 : return String.format("- %s = %s", key, oldVal);
174 : case UNMODIFIED:
175 0 : return String.format(" %s = %s", key, newVal);
176 : default:
177 0 : throw new IllegalStateException("Unexpected UpdateType: " + getUpdateType().name());
178 : }
179 : }
180 :
181 : public ConfigEntryType getUpdateType() {
182 0 : if (oldVal == null && newVal != null) {
183 0 : return ConfigEntryType.ADDED;
184 : }
185 0 : if (oldVal != null && newVal == null) {
186 0 : return ConfigEntryType.REMOVED;
187 : }
188 0 : if (Objects.equals(oldVal, newVal)) {
189 0 : return ConfigEntryType.UNMODIFIED;
190 : }
191 0 : return ConfigEntryType.MODIFIED;
192 : }
193 : }
194 : }
|