LCOV - code coverage report
Current view: top level - server/account - StoredPreferences.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 98 122 80.3 %
Date: 2022-11-19 15:00:39 Functions: 19 21 90.5 %

          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             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750