com.google.gerrit.server.account.StoredPreferences Maven / Gradle / Ivy
// Copyright (C) 2018 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.account;
import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.server.config.ConfigUtil.loadSection;
import static com.google.gerrit.server.config.ConfigUtil.skipField;
import static com.google.gerrit.server.config.ConfigUtil.storeSection;
import static com.google.gerrit.server.git.UserConfigSections.CHANGE_TABLE;
import static com.google.gerrit.server.git.UserConfigSections.CHANGE_TABLE_COLUMN;
import static com.google.gerrit.server.git.UserConfigSections.KEY_ID;
import static com.google.gerrit.server.git.UserConfigSections.KEY_TARGET;
import static com.google.gerrit.server.git.UserConfigSections.KEY_URL;
import static java.util.Objects.requireNonNull;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.extensions.client.EditPreferencesInfo;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.extensions.client.MenuItem;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.UserConfigSections;
import com.google.gerrit.server.git.ValidationError;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.git.meta.VersionedMetaData;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Repository;
/**
* Parses/writes preferences from/to a {@link Config} file.
*
* This is a low-level API. Read/write of preferences in a user branch should be done through
* {@link AccountsUpdate} or {@link AccountConfig}.
*
*
The config file has separate sections for general, diff and edit preferences:
*
*
* [diff]
* hideTopMenu = true
* [edit]
* lineLength = 80
*
*
* The parameter names match the names that are used in the preferences REST API.
*
*
If the preference is omitted in the config file, then the default value for the preference is
* used.
*
*
Defaults for preferences that apply for all accounts can be configured in the {@code
* refs/users/default} branch in the {@code All-Users} repository. The config for the default
* preferences must be provided to this class so that it can read default values from it.
*
*
The preferences are lazily parsed.
*/
public class StoredPreferences {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
public static final String PREFERENCES_CONFIG = "preferences.config";
private final Account.Id accountId;
private final Config cfg;
private final Config defaultCfg;
private final ValidationError.Sink validationErrorSink;
private GeneralPreferencesInfo generalPreferences;
private DiffPreferencesInfo diffPreferences;
private EditPreferencesInfo editPreferences;
StoredPreferences(
Account.Id accountId,
Config cfg,
Config defaultCfg,
ValidationError.Sink validationErrorSink) {
this.accountId = requireNonNull(accountId, "accountId");
this.cfg = requireNonNull(cfg, "cfg");
this.defaultCfg = requireNonNull(defaultCfg, "defaultCfg");
this.validationErrorSink = requireNonNull(validationErrorSink, "validationErrorSink");
}
public GeneralPreferencesInfo getGeneralPreferences() {
if (generalPreferences == null) {
parse();
}
return generalPreferences;
}
public DiffPreferencesInfo getDiffPreferences() {
if (diffPreferences == null) {
parse();
}
return diffPreferences;
}
public EditPreferencesInfo getEditPreferences() {
if (editPreferences == null) {
parse();
}
return editPreferences;
}
public void parse() {
generalPreferences = parseGeneralPreferences(null);
diffPreferences = parseDiffPreferences(null);
editPreferences = parseEditPreferences(null);
}
public Config saveGeneralPreferences(
Optional generalPreferencesInput,
Optional diffPreferencesInput,
Optional editPreferencesInput)
throws ConfigInvalidException {
if (generalPreferencesInput.isPresent()) {
GeneralPreferencesInfo mergedGeneralPreferencesInput =
parseGeneralPreferences(generalPreferencesInput.get());
storeSection(
cfg,
UserConfigSections.GENERAL,
null,
mergedGeneralPreferencesInput,
parseDefaultGeneralPreferences(defaultCfg, null));
setChangeTable(cfg, mergedGeneralPreferencesInput.changeTable);
setMy(cfg, mergedGeneralPreferencesInput.my);
// evict the cached general preferences
this.generalPreferences = null;
}
if (diffPreferencesInput.isPresent()) {
DiffPreferencesInfo mergedDiffPreferencesInput =
parseDiffPreferences(diffPreferencesInput.get());
storeSection(
cfg,
UserConfigSections.DIFF,
null,
mergedDiffPreferencesInput,
parseDefaultDiffPreferences(defaultCfg, null));
// evict the cached diff preferences
this.diffPreferences = null;
}
if (editPreferencesInput.isPresent()) {
EditPreferencesInfo mergedEditPreferencesInput =
parseEditPreferences(editPreferencesInput.get());
storeSection(
cfg,
UserConfigSections.EDIT,
null,
mergedEditPreferencesInput,
parseDefaultEditPreferences(defaultCfg, null));
// evict the cached edit preferences
this.editPreferences = null;
}
return cfg;
}
private GeneralPreferencesInfo parseGeneralPreferences(@Nullable GeneralPreferencesInfo input) {
try {
return parseGeneralPreferences(cfg, defaultCfg, input);
} catch (ConfigInvalidException e) {
validationErrorSink.error(
new ValidationError(
PREFERENCES_CONFIG,
String.format(
"Invalid general preferences for account %d: %s",
accountId.get(), e.getMessage())));
return new GeneralPreferencesInfo();
}
}
private DiffPreferencesInfo parseDiffPreferences(@Nullable DiffPreferencesInfo input) {
try {
return parseDiffPreferences(cfg, defaultCfg, input);
} catch (ConfigInvalidException e) {
validationErrorSink.error(
new ValidationError(
PREFERENCES_CONFIG,
String.format(
"Invalid diff preferences for account %d: %s", accountId.get(), e.getMessage())));
return new DiffPreferencesInfo();
}
}
private EditPreferencesInfo parseEditPreferences(@Nullable EditPreferencesInfo input) {
try {
return parseEditPreferences(cfg, defaultCfg, input);
} catch (ConfigInvalidException e) {
validationErrorSink.error(
new ValidationError(
PREFERENCES_CONFIG,
String.format(
"Invalid edit preferences for account %d: %s", accountId.get(), e.getMessage())));
return new EditPreferencesInfo();
}
}
private static GeneralPreferencesInfo parseGeneralPreferences(
Config cfg, @Nullable Config defaultCfg, @Nullable GeneralPreferencesInfo input)
throws ConfigInvalidException {
GeneralPreferencesInfo r =
loadSection(
cfg,
UserConfigSections.GENERAL,
null,
new GeneralPreferencesInfo(),
defaultCfg != null
? parseDefaultGeneralPreferences(defaultCfg, input)
: GeneralPreferencesInfo.defaults(),
input);
if (input != null) {
r.changeTable = input.changeTable;
r.my = input.my;
} else {
r.changeTable = parseChangeTableColumns(cfg, defaultCfg);
r.my = parseMyMenus(cfg, defaultCfg);
}
return r;
}
private static DiffPreferencesInfo parseDiffPreferences(
Config cfg, @Nullable Config defaultCfg, @Nullable DiffPreferencesInfo input)
throws ConfigInvalidException {
return loadSection(
cfg,
UserConfigSections.DIFF,
null,
new DiffPreferencesInfo(),
defaultCfg != null
? parseDefaultDiffPreferences(defaultCfg, input)
: DiffPreferencesInfo.defaults(),
input);
}
private static EditPreferencesInfo parseEditPreferences(
Config cfg, @Nullable Config defaultCfg, @Nullable EditPreferencesInfo input)
throws ConfigInvalidException {
return loadSection(
cfg,
UserConfigSections.EDIT,
null,
new EditPreferencesInfo(),
defaultCfg != null
? parseDefaultEditPreferences(defaultCfg, input)
: EditPreferencesInfo.defaults(),
input);
}
private static GeneralPreferencesInfo parseDefaultGeneralPreferences(
Config defaultCfg, GeneralPreferencesInfo input) throws ConfigInvalidException {
GeneralPreferencesInfo allUserPrefs = new GeneralPreferencesInfo();
loadSection(
defaultCfg,
UserConfigSections.GENERAL,
null,
allUserPrefs,
GeneralPreferencesInfo.defaults(),
input);
return updateGeneralPreferencesDefaults(allUserPrefs);
}
private static DiffPreferencesInfo parseDefaultDiffPreferences(
Config defaultCfg, DiffPreferencesInfo input) throws ConfigInvalidException {
DiffPreferencesInfo allUserPrefs = new DiffPreferencesInfo();
loadSection(
defaultCfg,
UserConfigSections.DIFF,
null,
allUserPrefs,
DiffPreferencesInfo.defaults(),
input);
return updateDiffPreferencesDefaults(allUserPrefs);
}
private static EditPreferencesInfo parseDefaultEditPreferences(
Config defaultCfg, EditPreferencesInfo input) throws ConfigInvalidException {
EditPreferencesInfo allUserPrefs = new EditPreferencesInfo();
loadSection(
defaultCfg,
UserConfigSections.EDIT,
null,
allUserPrefs,
EditPreferencesInfo.defaults(),
input);
return updateEditPreferencesDefaults(allUserPrefs);
}
private static GeneralPreferencesInfo updateGeneralPreferencesDefaults(
GeneralPreferencesInfo input) {
GeneralPreferencesInfo result = GeneralPreferencesInfo.defaults();
try {
for (Field field : input.getClass().getDeclaredFields()) {
if (skipField(field)) {
continue;
}
Object newVal = field.get(input);
if (newVal != null) {
field.set(result, newVal);
}
}
} catch (IllegalAccessException e) {
logger.atSevere().withCause(e).log("Failed to apply default general preferences");
return GeneralPreferencesInfo.defaults();
}
return result;
}
private static DiffPreferencesInfo updateDiffPreferencesDefaults(DiffPreferencesInfo input) {
DiffPreferencesInfo result = DiffPreferencesInfo.defaults();
try {
for (Field field : input.getClass().getDeclaredFields()) {
if (skipField(field)) {
continue;
}
Object newVal = field.get(input);
if (newVal != null) {
field.set(result, newVal);
}
}
} catch (IllegalAccessException e) {
logger.atSevere().withCause(e).log("Failed to apply default diff preferences");
return DiffPreferencesInfo.defaults();
}
return result;
}
private static EditPreferencesInfo updateEditPreferencesDefaults(EditPreferencesInfo input) {
EditPreferencesInfo result = EditPreferencesInfo.defaults();
try {
for (Field field : input.getClass().getDeclaredFields()) {
if (skipField(field)) {
continue;
}
Object newVal = field.get(input);
if (newVal != null) {
field.set(result, newVal);
}
}
} catch (IllegalAccessException e) {
logger.atSevere().withCause(e).log("Failed to apply default edit preferences");
return EditPreferencesInfo.defaults();
}
return result;
}
private static List parseChangeTableColumns(Config cfg, @Nullable Config defaultCfg) {
List changeTable = changeTable(cfg);
if (changeTable == null && defaultCfg != null) {
changeTable = changeTable(defaultCfg);
}
return changeTable;
}
private static List