diff options
Diffstat (limited to 'java/com/google/gerrit/server/restapi/project/PutConfig.java')
-rw-r--r-- | java/com/google/gerrit/server/restapi/project/PutConfig.java | 290 |
1 files changed, 290 insertions, 0 deletions
diff --git a/java/com/google/gerrit/server/restapi/project/PutConfig.java b/java/com/google/gerrit/server/restapi/project/PutConfig.java new file mode 100644 index 0000000000..76ea0c9125 --- /dev/null +++ b/java/com/google/gerrit/server/restapi/project/PutConfig.java @@ -0,0 +1,290 @@ +// Copyright (C) 2013 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.restapi.project; + +import com.google.common.base.Joiner; +import com.google.common.base.Strings; +import com.google.common.flogger.FluentLogger; +import com.google.gerrit.extensions.api.projects.ConfigInfo; +import com.google.gerrit.extensions.api.projects.ConfigInput; +import com.google.gerrit.extensions.api.projects.ConfigValue; +import com.google.gerrit.extensions.api.projects.ProjectConfigEntryType; +import com.google.gerrit.extensions.client.InheritableBoolean; +import com.google.gerrit.extensions.registration.DynamicMap; +import com.google.gerrit.extensions.restapi.BadRequestException; +import com.google.gerrit.extensions.restapi.ResourceConflictException; +import com.google.gerrit.extensions.restapi.ResourceNotFoundException; +import com.google.gerrit.extensions.restapi.RestApiException; +import com.google.gerrit.extensions.restapi.RestModifyView; +import com.google.gerrit.extensions.restapi.RestView; +import com.google.gerrit.reviewdb.client.BooleanProjectConfig; +import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.server.CurrentUser; +import com.google.gerrit.server.EnableSignedPush; +import com.google.gerrit.server.config.AllProjectsName; +import com.google.gerrit.server.config.PluginConfig; +import com.google.gerrit.server.config.PluginConfigFactory; +import com.google.gerrit.server.config.ProjectConfigEntry; +import com.google.gerrit.server.extensions.webui.UiActions; +import com.google.gerrit.server.git.meta.MetaDataUpdate; +import com.google.gerrit.server.permissions.PermissionBackend; +import com.google.gerrit.server.permissions.PermissionBackendException; +import com.google.gerrit.server.permissions.ProjectPermission; +import com.google.gerrit.server.project.BooleanProjectConfigTransformations; +import com.google.gerrit.server.project.ProjectCache; +import com.google.gerrit.server.project.ProjectConfig; +import com.google.gerrit.server.project.ProjectResource; +import com.google.gerrit.server.project.ProjectState; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Pattern; +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.errors.RepositoryNotFoundException; + +@Singleton +public class PutConfig implements RestModifyView<ProjectResource, ConfigInput> { + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + + private static final Pattern PARAMETER_NAME_PATTERN = + Pattern.compile("^[a-zA-Z0-9]+[a-zA-Z0-9-]*$"); + + private final boolean serverEnableSignedPush; + private final Provider<MetaDataUpdate.User> metaDataUpdateFactory; + private final ProjectCache projectCache; + private final ProjectState.Factory projectStateFactory; + private final DynamicMap<ProjectConfigEntry> pluginConfigEntries; + private final PluginConfigFactory cfgFactory; + private final AllProjectsName allProjects; + private final UiActions uiActions; + private final DynamicMap<RestView<ProjectResource>> views; + private final Provider<CurrentUser> user; + private final PermissionBackend permissionBackend; + + @Inject + PutConfig( + @EnableSignedPush boolean serverEnableSignedPush, + Provider<MetaDataUpdate.User> metaDataUpdateFactory, + ProjectCache projectCache, + ProjectState.Factory projectStateFactory, + DynamicMap<ProjectConfigEntry> pluginConfigEntries, + PluginConfigFactory cfgFactory, + AllProjectsName allProjects, + UiActions uiActions, + DynamicMap<RestView<ProjectResource>> views, + Provider<CurrentUser> user, + PermissionBackend permissionBackend) { + this.serverEnableSignedPush = serverEnableSignedPush; + this.metaDataUpdateFactory = metaDataUpdateFactory; + this.projectCache = projectCache; + this.projectStateFactory = projectStateFactory; + this.pluginConfigEntries = pluginConfigEntries; + this.cfgFactory = cfgFactory; + this.allProjects = allProjects; + this.uiActions = uiActions; + this.views = views; + this.user = user; + this.permissionBackend = permissionBackend; + } + + @Override + public ConfigInfo apply(ProjectResource rsrc, ConfigInput input) + throws RestApiException, PermissionBackendException { + permissionBackend + .currentUser() + .project(rsrc.getNameKey()) + .check(ProjectPermission.WRITE_CONFIG); + return apply(rsrc.getProjectState(), input); + } + + public ConfigInfo apply(ProjectState projectState, ConfigInput input) + throws ResourceNotFoundException, BadRequestException, ResourceConflictException { + Project.NameKey projectName = projectState.getNameKey(); + if (input == null) { + throw new BadRequestException("config is required"); + } + + try (MetaDataUpdate md = metaDataUpdateFactory.get().create(projectName)) { + ProjectConfig projectConfig = ProjectConfig.read(md); + Project p = projectConfig.getProject(); + + p.setDescription(Strings.emptyToNull(input.description)); + + for (BooleanProjectConfig cfg : BooleanProjectConfig.values()) { + InheritableBoolean val = BooleanProjectConfigTransformations.get(cfg, input); + if (val != null) { + p.setBooleanConfig(cfg, val); + } + } + + if (input.maxObjectSizeLimit != null) { + p.setMaxObjectSizeLimit(input.maxObjectSizeLimit); + } + + if (input.submitType != null) { + p.setSubmitType(input.submitType); + } + + if (input.state != null) { + p.setState(input.state); + } + + if (input.pluginConfigValues != null) { + setPluginConfigValues(projectState, projectConfig, input.pluginConfigValues); + } + + md.setMessage("Modified project settings\n"); + try { + projectConfig.commit(md); + projectCache.evict(projectConfig.getProject()); + md.getRepository().setGitwebDescription(p.getDescription()); + } catch (IOException e) { + if (e.getCause() instanceof ConfigInvalidException) { + throw new ResourceConflictException( + "Cannot update " + projectName + ": " + e.getCause().getMessage()); + } + logger.atWarning().withCause(e).log("Failed to update config of project %s.", projectName); + throw new ResourceConflictException("Cannot update " + projectName); + } + + ProjectState state = projectStateFactory.create(ProjectConfig.read(md)); + return new ConfigInfoImpl( + serverEnableSignedPush, + state, + user.get(), + pluginConfigEntries, + cfgFactory, + allProjects, + uiActions, + views); + } catch (RepositoryNotFoundException notFound) { + throw new ResourceNotFoundException(projectName.get()); + } catch (ConfigInvalidException err) { + throw new ResourceConflictException("Cannot read project " + projectName, err); + } catch (IOException err) { + throw new ResourceConflictException("Cannot update project " + projectName, err); + } + } + + private void setPluginConfigValues( + ProjectState projectState, + ProjectConfig projectConfig, + Map<String, Map<String, ConfigValue>> pluginConfigValues) + throws BadRequestException { + for (Entry<String, Map<String, ConfigValue>> e : pluginConfigValues.entrySet()) { + String pluginName = e.getKey(); + PluginConfig cfg = projectConfig.getPluginConfig(pluginName); + for (Entry<String, ConfigValue> v : e.getValue().entrySet()) { + ProjectConfigEntry projectConfigEntry = pluginConfigEntries.get(pluginName, v.getKey()); + if (projectConfigEntry != null) { + if (!PARAMETER_NAME_PATTERN.matcher(v.getKey()).matches()) { + // TODO check why we have this restriction + logger.atWarning().log( + "Parameter name '%s' must match '%s'", + v.getKey(), PARAMETER_NAME_PATTERN.pattern()); + continue; + } + String oldValue = cfg.getString(v.getKey()); + String value = v.getValue().value; + if (projectConfigEntry.getType() == ProjectConfigEntryType.ARRAY) { + List<String> l = Arrays.asList(cfg.getStringList(v.getKey())); + oldValue = Joiner.on("\n").join(l); + value = Joiner.on("\n").join(v.getValue().values); + } + if (Strings.emptyToNull(value) != null) { + if (!value.equals(oldValue)) { + validateProjectConfigEntryIsEditable( + projectConfigEntry, projectState, v.getKey(), pluginName); + v.setValue(projectConfigEntry.preUpdate(v.getValue())); + value = v.getValue().value; + try { + switch (projectConfigEntry.getType()) { + case BOOLEAN: + boolean newBooleanValue = Boolean.parseBoolean(value); + cfg.setBoolean(v.getKey(), newBooleanValue); + break; + case INT: + int newIntValue = Integer.parseInt(value); + cfg.setInt(v.getKey(), newIntValue); + break; + case LONG: + long newLongValue = Long.parseLong(value); + cfg.setLong(v.getKey(), newLongValue); + break; + case LIST: + if (!projectConfigEntry.getPermittedValues().contains(value)) { + throw new BadRequestException( + String.format( + "The value '%s' is not permitted for parameter '%s' of plugin '" + + pluginName + + "'", + value, + v.getKey())); + } + // $FALL-THROUGH$ + case STRING: + cfg.setString(v.getKey(), value); + break; + case ARRAY: + cfg.setStringList(v.getKey(), v.getValue().values); + break; + default: + logger.atWarning().log( + "The type '%s' of parameter '%s' is not supported.", + projectConfigEntry.getType().name(), v.getKey()); + } + } catch (NumberFormatException ex) { + throw new BadRequestException( + String.format( + "The value '%s' of config parameter '%s' of plugin '%s' is invalid: %s", + v.getValue(), v.getKey(), pluginName, ex.getMessage())); + } + } + } else { + if (oldValue != null) { + validateProjectConfigEntryIsEditable( + projectConfigEntry, projectState, v.getKey(), pluginName); + cfg.unset(v.getKey()); + } + } + } else { + throw new BadRequestException( + String.format( + "The config parameter '%s' of plugin '%s' does not exist.", + v.getKey(), pluginName)); + } + } + } + } + + private static void validateProjectConfigEntryIsEditable( + ProjectConfigEntry projectConfigEntry, + ProjectState projectState, + String parameterName, + String pluginName) + throws BadRequestException { + if (!projectConfigEntry.isEditable(projectState)) { + throw new BadRequestException( + String.format( + "Not allowed to set parameter '%s' of plugin '%s' on project '%s'.", + parameterName, pluginName, projectState.getName())); + } + } +} |