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 :
15 : package com.google.gerrit.server.account;
16 :
17 : import static com.google.common.base.Preconditions.checkState;
18 : import static com.google.gerrit.server.config.ConfigUtil.storeSection;
19 : import static com.google.gerrit.server.git.UserConfigSections.CHANGE_TABLE_COLUMN;
20 : import static com.google.gerrit.server.git.UserConfigSections.KEY_ID;
21 : import static com.google.gerrit.server.git.UserConfigSections.KEY_TARGET;
22 : import static com.google.gerrit.server.git.UserConfigSections.KEY_URL;
23 : import static java.util.Objects.requireNonNull;
24 :
25 : import com.google.gerrit.common.Nullable;
26 : import com.google.gerrit.entities.Account;
27 : import com.google.gerrit.extensions.client.DiffPreferencesInfo;
28 : import com.google.gerrit.extensions.client.EditPreferencesInfo;
29 : import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
30 : import com.google.gerrit.extensions.client.MenuItem;
31 : import com.google.gerrit.extensions.restapi.BadRequestException;
32 : import com.google.gerrit.server.config.AllUsersName;
33 : import com.google.gerrit.server.config.PreferencesParserUtil;
34 : import com.google.gerrit.server.config.VersionedDefaultPreferences;
35 : import com.google.gerrit.server.git.UserConfigSections;
36 : import com.google.gerrit.server.git.ValidationError;
37 : import com.google.gerrit.server.git.meta.MetaDataUpdate;
38 : import java.io.IOException;
39 : import java.util.List;
40 : import java.util.Optional;
41 : import org.eclipse.jgit.errors.ConfigInvalidException;
42 : import org.eclipse.jgit.lib.Config;
43 : import org.eclipse.jgit.lib.Repository;
44 :
45 : /**
46 : * Parses/writes preferences from/to a {@link Config} file.
47 : *
48 : * <p>This is a low-level API. Read/write of preferences in a user branch should be done through
49 : * {@link AccountsUpdate} or {@link AccountConfig}.
50 : *
51 : * <p>The config file has separate sections for general, diff and edit preferences:
52 : *
53 : * <pre>
54 : * [diff]
55 : * hideTopMenu = true
56 : * [edit]
57 : * lineLength = 80
58 : * </pre>
59 : *
60 : * <p>The parameter names match the names that are used in the preferences REST API.
61 : *
62 : * <p>If the preference is omitted in the config file, then the default value for the preference is
63 : * used.
64 : *
65 : * <p>Defaults for preferences that apply for all accounts can be configured in the {@code
66 : * refs/users/default} branch in the {@code All-Users} repository. The config for the default
67 : * preferences must be provided to this class so that it can read default values from it.
68 : *
69 : * <p>The preferences are lazily parsed.
70 : */
71 : public class StoredPreferences {
72 : public static final String PREFERENCES_CONFIG = "preferences.config";
73 :
74 : private final Account.Id accountId;
75 : private final Config cfg;
76 : private final Config defaultCfg;
77 : private final ValidationError.Sink validationErrorSink;
78 :
79 : private GeneralPreferencesInfo generalPreferences;
80 : private DiffPreferencesInfo diffPreferences;
81 : private EditPreferencesInfo editPreferences;
82 :
83 : StoredPreferences(
84 : Account.Id accountId,
85 : Config cfg,
86 : Config defaultCfg,
87 151 : ValidationError.Sink validationErrorSink) {
88 151 : this.accountId = requireNonNull(accountId, "accountId");
89 151 : this.cfg = requireNonNull(cfg, "cfg");
90 151 : this.defaultCfg = requireNonNull(defaultCfg, "defaultCfg");
91 151 : this.validationErrorSink = requireNonNull(validationErrorSink, "validationErrorSink");
92 151 : }
93 :
94 : public GeneralPreferencesInfo getGeneralPreferences() {
95 1 : if (generalPreferences == null) {
96 1 : parse();
97 : }
98 1 : return generalPreferences;
99 : }
100 :
101 : public DiffPreferencesInfo getDiffPreferences() {
102 0 : if (diffPreferences == null) {
103 0 : parse();
104 : }
105 0 : return diffPreferences;
106 : }
107 :
108 : public EditPreferencesInfo getEditPreferences() {
109 0 : if (editPreferences == null) {
110 0 : parse();
111 : }
112 0 : return editPreferences;
113 : }
114 :
115 : public void parse() {
116 151 : generalPreferences = parseGeneralPreferences(null);
117 151 : diffPreferences = parseDiffPreferences(null);
118 151 : editPreferences = parseEditPreferences(null);
119 151 : }
120 :
121 : public Config saveGeneralPreferences(
122 : Optional<GeneralPreferencesInfo> generalPreferencesInput,
123 : Optional<DiffPreferencesInfo> diffPreferencesInput,
124 : Optional<EditPreferencesInfo> editPreferencesInput)
125 : throws ConfigInvalidException {
126 8 : if (generalPreferencesInput.isPresent()) {
127 8 : GeneralPreferencesInfo mergedGeneralPreferencesInput =
128 8 : parseGeneralPreferences(generalPreferencesInput.get());
129 :
130 8 : storeSection(
131 : cfg,
132 : UserConfigSections.GENERAL,
133 : null,
134 : mergedGeneralPreferencesInput,
135 8 : PreferencesParserUtil.parseDefaultGeneralPreferences(defaultCfg, null));
136 8 : setChangeTable(cfg, mergedGeneralPreferencesInput.changeTable);
137 8 : setMy(cfg, mergedGeneralPreferencesInput.my);
138 :
139 : // evict the cached general preferences
140 8 : this.generalPreferences = null;
141 : }
142 :
143 8 : if (diffPreferencesInput.isPresent()) {
144 1 : DiffPreferencesInfo mergedDiffPreferencesInput =
145 1 : parseDiffPreferences(diffPreferencesInput.get());
146 :
147 1 : storeSection(
148 : cfg,
149 : UserConfigSections.DIFF,
150 : null,
151 : mergedDiffPreferencesInput,
152 1 : PreferencesParserUtil.parseDefaultDiffPreferences(defaultCfg, null));
153 :
154 : // evict the cached diff preferences
155 1 : this.diffPreferences = null;
156 : }
157 :
158 8 : if (editPreferencesInput.isPresent()) {
159 1 : EditPreferencesInfo mergedEditPreferencesInput =
160 1 : parseEditPreferences(editPreferencesInput.get());
161 :
162 1 : storeSection(
163 : cfg,
164 : UserConfigSections.EDIT,
165 : null,
166 : mergedEditPreferencesInput,
167 1 : PreferencesParserUtil.parseDefaultEditPreferences(defaultCfg, null));
168 :
169 : // evict the cached edit preferences
170 1 : this.editPreferences = null;
171 : }
172 :
173 8 : return cfg;
174 : }
175 :
176 : /** Returns the content of the {@code preferences.config} file as {@link Config}. */
177 : Config getRaw() {
178 151 : return cfg;
179 : }
180 :
181 : private GeneralPreferencesInfo parseGeneralPreferences(@Nullable GeneralPreferencesInfo input) {
182 : try {
183 151 : return PreferencesParserUtil.parseGeneralPreferences(cfg, defaultCfg, input);
184 0 : } catch (ConfigInvalidException e) {
185 0 : validationErrorSink.error(
186 0 : ValidationError.create(
187 : PREFERENCES_CONFIG,
188 0 : String.format(
189 : "Invalid general preferences for account %d: %s",
190 0 : accountId.get(), e.getMessage())));
191 0 : return new GeneralPreferencesInfo();
192 : }
193 : }
194 :
195 : private DiffPreferencesInfo parseDiffPreferences(@Nullable DiffPreferencesInfo input) {
196 : try {
197 151 : return PreferencesParserUtil.parseDiffPreferences(cfg, defaultCfg, input);
198 0 : } catch (ConfigInvalidException e) {
199 0 : validationErrorSink.error(
200 0 : ValidationError.create(
201 : PREFERENCES_CONFIG,
202 0 : String.format(
203 0 : "Invalid diff preferences for account %d: %s", accountId.get(), e.getMessage())));
204 0 : return new DiffPreferencesInfo();
205 : }
206 : }
207 :
208 : private EditPreferencesInfo parseEditPreferences(@Nullable EditPreferencesInfo input) {
209 : try {
210 151 : return PreferencesParserUtil.parseEditPreferences(cfg, defaultCfg, input);
211 0 : } catch (ConfigInvalidException e) {
212 0 : validationErrorSink.error(
213 0 : ValidationError.create(
214 : PREFERENCES_CONFIG,
215 0 : String.format(
216 0 : "Invalid edit preferences for account %d: %s", accountId.get(), e.getMessage())));
217 0 : return new EditPreferencesInfo();
218 : }
219 : }
220 :
221 : public static GeneralPreferencesInfo updateDefaultGeneralPreferences(
222 : MetaDataUpdate md, GeneralPreferencesInfo input) throws IOException, ConfigInvalidException {
223 2 : VersionedDefaultPreferences defaultPrefs = new VersionedDefaultPreferences();
224 2 : defaultPrefs.load(md);
225 2 : storeSection(
226 2 : defaultPrefs.getConfig(),
227 : UserConfigSections.GENERAL,
228 : null,
229 : input,
230 2 : GeneralPreferencesInfo.defaults());
231 2 : setMy(defaultPrefs.getConfig(), input.my);
232 2 : setChangeTable(defaultPrefs.getConfig(), input.changeTable);
233 2 : defaultPrefs.commit(md);
234 :
235 2 : return PreferencesParserUtil.parseGeneralPreferences(defaultPrefs.getConfig(), null, null);
236 : }
237 :
238 : public static DiffPreferencesInfo updateDefaultDiffPreferences(
239 : MetaDataUpdate md, DiffPreferencesInfo input) throws IOException, ConfigInvalidException {
240 2 : VersionedDefaultPreferences defaultPrefs = new VersionedDefaultPreferences();
241 2 : defaultPrefs.load(md);
242 2 : storeSection(
243 2 : defaultPrefs.getConfig(),
244 : UserConfigSections.DIFF,
245 : null,
246 : input,
247 2 : DiffPreferencesInfo.defaults());
248 2 : defaultPrefs.commit(md);
249 :
250 2 : return PreferencesParserUtil.parseDiffPreferences(defaultPrefs.getConfig(), null, null);
251 : }
252 :
253 : public static EditPreferencesInfo updateDefaultEditPreferences(
254 : MetaDataUpdate md, EditPreferencesInfo input) throws IOException, ConfigInvalidException {
255 1 : VersionedDefaultPreferences defaultPrefs = new VersionedDefaultPreferences();
256 1 : defaultPrefs.load(md);
257 1 : storeSection(
258 1 : defaultPrefs.getConfig(),
259 : UserConfigSections.EDIT,
260 : null,
261 : input,
262 1 : EditPreferencesInfo.defaults());
263 1 : defaultPrefs.commit(md);
264 :
265 1 : return PreferencesParserUtil.parseEditPreferences(defaultPrefs.getConfig(), null, null);
266 : }
267 :
268 : public static void validateMy(List<MenuItem> my) throws BadRequestException {
269 9 : if (my == null) {
270 5 : return;
271 : }
272 6 : for (MenuItem item : my) {
273 6 : checkRequiredMenuItemField(item.name, "name");
274 6 : checkRequiredMenuItemField(item.url, "URL");
275 6 : }
276 6 : }
277 :
278 : static Config readDefaultConfig(AllUsersName allUsersName, Repository allUsersRepo)
279 : throws IOException, ConfigInvalidException {
280 151 : VersionedDefaultPreferences defaultPrefs = new VersionedDefaultPreferences();
281 151 : defaultPrefs.load(allUsersName, allUsersRepo);
282 151 : return defaultPrefs.getConfig();
283 : }
284 :
285 : private static void setChangeTable(Config cfg, List<String> changeTable) {
286 9 : if (changeTable != null) {
287 6 : unsetSection(cfg, UserConfigSections.CHANGE_TABLE);
288 6 : cfg.setStringList(UserConfigSections.CHANGE_TABLE, null, CHANGE_TABLE_COLUMN, changeTable);
289 : }
290 9 : }
291 :
292 : private static void setMy(Config cfg, List<MenuItem> my) {
293 9 : if (my != null) {
294 6 : unsetSection(cfg, UserConfigSections.MY);
295 6 : for (MenuItem item : my) {
296 6 : checkState(!isNullOrEmpty(item.name), "MenuItem.name must not be null or empty");
297 6 : checkState(!isNullOrEmpty(item.url), "MenuItem.url must not be null or empty");
298 :
299 6 : setMy(cfg, item.name, KEY_URL, item.url);
300 6 : setMy(cfg, item.name, KEY_TARGET, item.target);
301 6 : setMy(cfg, item.name, KEY_ID, item.id);
302 6 : }
303 : }
304 9 : }
305 :
306 : private static void checkRequiredMenuItemField(String value, String name)
307 : throws BadRequestException {
308 6 : if (isNullOrEmpty(value)) {
309 1 : throw new BadRequestException(name + " for menu item is required");
310 : }
311 6 : }
312 :
313 : private static boolean isNullOrEmpty(String value) {
314 6 : return value == null || value.trim().isEmpty();
315 : }
316 :
317 : private static void setMy(Config cfg, String section, String key, @Nullable String val) {
318 6 : if (val == null || val.trim().isEmpty()) {
319 6 : cfg.unset(UserConfigSections.MY, section.trim(), key);
320 : } else {
321 6 : cfg.setString(UserConfigSections.MY, section.trim(), key, val.trim());
322 : }
323 6 : }
324 :
325 : private static void unsetSection(Config cfg, String section) {
326 6 : cfg.unsetSection(section, null);
327 6 : for (String subsection : cfg.getSubsections(section)) {
328 5 : cfg.unsetSection(section, subsection);
329 5 : }
330 6 : }
331 : }
|