Line data Source code
1 : // Copyright (C) 2019 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.restapi.project; 16 : 17 : import com.google.common.base.Strings; 18 : import com.google.gerrit.entities.LabelType; 19 : import com.google.gerrit.extensions.common.LabelDefinitionInfo; 20 : import com.google.gerrit.extensions.common.LabelDefinitionInput; 21 : import com.google.gerrit.extensions.restapi.AuthException; 22 : import com.google.gerrit.extensions.restapi.BadRequestException; 23 : import com.google.gerrit.extensions.restapi.ResourceConflictException; 24 : import com.google.gerrit.extensions.restapi.Response; 25 : import com.google.gerrit.extensions.restapi.RestModifyView; 26 : import com.google.gerrit.index.query.QueryParseException; 27 : import com.google.gerrit.server.CurrentUser; 28 : import com.google.gerrit.server.git.meta.MetaDataUpdate; 29 : import com.google.gerrit.server.permissions.PermissionBackend; 30 : import com.google.gerrit.server.permissions.PermissionBackendException; 31 : import com.google.gerrit.server.permissions.ProjectPermission; 32 : import com.google.gerrit.server.project.LabelDefinitionJson; 33 : import com.google.gerrit.server.project.LabelResource; 34 : import com.google.gerrit.server.project.ProjectCache; 35 : import com.google.gerrit.server.project.ProjectConfig; 36 : import com.google.gerrit.server.query.approval.ApprovalQueryBuilder; 37 : import com.google.inject.Inject; 38 : import com.google.inject.Provider; 39 : import com.google.inject.Singleton; 40 : import java.io.IOException; 41 : import java.util.Optional; 42 : import org.eclipse.jgit.errors.ConfigInvalidException; 43 : 44 : @Singleton 45 : public class SetLabel implements RestModifyView<LabelResource, LabelDefinitionInput> { 46 : private final Provider<CurrentUser> user; 47 : private final PermissionBackend permissionBackend; 48 : private final MetaDataUpdate.User updateFactory; 49 : private final ProjectConfig.Factory projectConfigFactory; 50 : private final ProjectCache projectCache; 51 : private final ApprovalQueryBuilder approvalQueryBuilder; 52 : 53 : @Inject 54 : public SetLabel( 55 : Provider<CurrentUser> user, 56 : PermissionBackend permissionBackend, 57 : MetaDataUpdate.User updateFactory, 58 : ProjectConfig.Factory projectConfigFactory, 59 : ProjectCache projectCache, 60 146 : ApprovalQueryBuilder approvalQueryBuilder) { 61 146 : this.user = user; 62 146 : this.permissionBackend = permissionBackend; 63 146 : this.updateFactory = updateFactory; 64 146 : this.projectConfigFactory = projectConfigFactory; 65 146 : this.projectCache = projectCache; 66 146 : this.approvalQueryBuilder = approvalQueryBuilder; 67 146 : } 68 : 69 : @Override 70 : public Response<LabelDefinitionInfo> apply(LabelResource rsrc, LabelDefinitionInput input) 71 : throws AuthException, BadRequestException, ResourceConflictException, 72 : PermissionBackendException, IOException, ConfigInvalidException { 73 2 : if (!user.get().isIdentifiedUser()) { 74 0 : throw new AuthException("Authentication required"); 75 : } 76 : 77 2 : permissionBackend 78 2 : .currentUser() 79 2 : .project(rsrc.getProject().getNameKey()) 80 2 : .check(ProjectPermission.WRITE_CONFIG); 81 : 82 2 : if (input == null) { 83 0 : input = new LabelDefinitionInput(); 84 : } 85 : 86 2 : LabelType labelType = rsrc.getLabelType(); 87 : 88 2 : try (MetaDataUpdate md = updateFactory.create(rsrc.getProject().getNameKey())) { 89 2 : ProjectConfig config = projectConfigFactory.read(md); 90 : 91 2 : if (updateLabel(config, labelType, input)) { 92 1 : if (input.commitMessage != null) { 93 1 : md.setMessage(Strings.emptyToNull(input.commitMessage.trim())); 94 : } else { 95 1 : md.setMessage("Update label"); 96 : } 97 1 : String newName = Strings.nullToEmpty(input.name).trim(); 98 1 : labelType = 99 1 : config.getLabelSections().get(newName.isEmpty() ? labelType.getName() : newName); 100 : 101 1 : config.commit(md); 102 1 : projectCache.evictAndReindex(rsrc.getProject().getProjectState().getProject()); 103 : } 104 : } 105 2 : return Response.ok(LabelDefinitionJson.format(rsrc.getProject().getNameKey(), labelType)); 106 : } 107 : 108 : /** 109 : * Updates the given label. 110 : * 111 : * @param config the project config 112 : * @param labelType the label type that should be updated 113 : * @param input the input that describes the label update 114 : * @return whether the label type was modified 115 : * @throws BadRequestException if there was invalid data in the input 116 : * @throws ResourceConflictException if the update cannot be applied due to a conflict 117 : */ 118 : public boolean updateLabel(ProjectConfig config, LabelType labelType, LabelDefinitionInput input) 119 : throws BadRequestException, ResourceConflictException { 120 3 : boolean dirty = false; 121 3 : LabelType.Builder labelTypeBuilder = labelType.toBuilder(); 122 : 123 3 : if (input.name != null) { 124 2 : String newName = input.name.trim(); 125 2 : if (newName.isEmpty()) { 126 1 : throw new BadRequestException("name cannot be empty"); 127 : } 128 2 : if (!newName.equals(labelType.getName())) { 129 2 : if (config.getLabelSections().containsKey(newName)) { 130 1 : throw new ResourceConflictException(String.format("name %s already in use", newName)); 131 : } 132 : 133 2 : for (String labelName : config.getLabelSections().keySet()) { 134 2 : if (labelName.equalsIgnoreCase(newName)) { 135 1 : throw new ResourceConflictException( 136 1 : String.format("name %s conflicts with existing label %s", newName, labelName)); 137 : } 138 2 : } 139 : 140 : try { 141 2 : LabelType.checkName(newName); 142 1 : } catch (IllegalArgumentException e) { 143 1 : throw new BadRequestException("invalid name: " + input.name, e); 144 2 : } 145 : 146 2 : labelTypeBuilder.setName(newName); 147 2 : dirty = true; 148 : } 149 : } 150 : 151 3 : if (input.description != null) { 152 1 : String description = Strings.emptyToNull(input.description.trim()); 153 1 : labelTypeBuilder.setDescription(Optional.ofNullable(description)); 154 1 : dirty = true; 155 : } 156 : 157 3 : if (input.function != null) { 158 2 : if (input.function.trim().isEmpty()) { 159 1 : throw new BadRequestException("function cannot be empty"); 160 : } 161 2 : labelTypeBuilder.setFunction(LabelDefinitionInputParser.parseFunction(input.function)); 162 2 : dirty = true; 163 : } 164 : 165 3 : if (input.values != null) { 166 1 : if (input.values.isEmpty()) { 167 1 : throw new BadRequestException("values cannot be empty"); 168 : } 169 1 : labelTypeBuilder.setValues(LabelDefinitionInputParser.parseValues(input.values)); 170 1 : dirty = true; 171 : } 172 : 173 3 : if (input.defaultValue != null) { 174 1 : labelTypeBuilder.setDefaultValue( 175 1 : LabelDefinitionInputParser.parseDefaultValue(labelTypeBuilder, input.defaultValue)); 176 1 : dirty = true; 177 : } 178 : 179 3 : if (input.branches != null) { 180 1 : labelTypeBuilder.setRefPatterns(LabelDefinitionInputParser.parseBranches(input.branches)); 181 1 : dirty = true; 182 : } 183 : 184 3 : if (input.canOverride != null) { 185 1 : labelTypeBuilder.setCanOverride(input.canOverride); 186 1 : dirty = true; 187 : } 188 : 189 3 : input.copyCondition = Strings.emptyToNull(input.copyCondition); 190 3 : if (input.copyCondition != null) { 191 : try { 192 1 : approvalQueryBuilder.parse(input.copyCondition); 193 1 : } catch (QueryParseException e) { 194 1 : throw new BadRequestException( 195 1 : "unable to parse copy condition. got: " + input.copyCondition + ". " + e.getMessage(), 196 : e); 197 1 : } 198 1 : labelTypeBuilder.setCopyCondition(input.copyCondition); 199 1 : dirty = true; 200 1 : if (Boolean.TRUE.equals(input.unsetCopyCondition)) { 201 0 : throw new BadRequestException("can't set and unset copyCondition in the same request"); 202 : } 203 : } 204 3 : if (Boolean.TRUE.equals(input.unsetCopyCondition)) { 205 1 : labelTypeBuilder.setCopyCondition(null); 206 1 : dirty = true; 207 : } 208 : 209 3 : if (input.allowPostSubmit != null) { 210 1 : labelTypeBuilder.setAllowPostSubmit(input.allowPostSubmit); 211 1 : dirty = true; 212 : } 213 : 214 3 : if (input.ignoreSelfApproval != null) { 215 1 : labelTypeBuilder.setIgnoreSelfApproval(input.ignoreSelfApproval); 216 1 : dirty = true; 217 : } 218 : 219 3 : config.getLabelSections().remove(labelType.getName()); 220 3 : config.upsertLabelType(labelTypeBuilder.build()); 221 : 222 3 : return dirty; 223 : } 224 : }