blob: 0810c447aa78b67f7a3a7b1cb00e12abc8733719 [file] [log] [blame]
// Copyright (C) 2023 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.change;
import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.server.change.CustomKeyedValuesUtil.extractCustomKeyedValues;
import static com.google.gerrit.server.change.CustomKeyedValuesUtil.extractCustomKeys;
import static com.google.gerrit.server.notedb.ChangeUpdate.MAX_CUSTOM_KEYED_VALUES;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.api.changes.CustomKeyedValuesInput;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.server.change.CustomKeyedValuesUtil.InvalidCustomKeyedValueException;
import com.google.gerrit.server.extensions.events.CustomKeyedValuesEdited;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.gerrit.server.update.BatchUpdateOp;
import com.google.gerrit.server.update.ChangeContext;
import com.google.gerrit.server.update.PostUpdateContext;
import com.google.gerrit.server.validators.CustomKeyedValueValidationListener;
import com.google.gerrit.server.validators.ValidationException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class SetCustomKeyedValuesOp implements BatchUpdateOp {
public interface Factory {
SetCustomKeyedValuesOp create(CustomKeyedValuesInput input);
}
private final PluginSetContext<CustomKeyedValueValidationListener> validationListeners;
private final CustomKeyedValuesEdited customKeyedValuesEdited;
private final CustomKeyedValuesInput input;
private boolean fireEvent = true;
private Change change;
private ImmutableMap<String, String> toAdd;
private ImmutableSet<String> toRemove;
private ImmutableMap<String, String> updatedCustomKeyedValues;
@Inject
SetCustomKeyedValuesOp(
PluginSetContext<CustomKeyedValueValidationListener> validationListeners,
CustomKeyedValuesEdited customKeyedValuesEdited,
@Assisted @Nullable CustomKeyedValuesInput input) {
this.validationListeners = validationListeners;
this.customKeyedValuesEdited = customKeyedValuesEdited;
this.input = input;
}
public SetCustomKeyedValuesOp setFireEvent(boolean fireEvent) {
this.fireEvent = fireEvent;
return this;
}
@Override
public boolean updateChange(ChangeContext ctx)
throws AuthException, BadRequestException, MethodNotAllowedException, IOException {
if (input == null || (input.add == null && input.remove == null)) {
updatedCustomKeyedValues = ImmutableMap.of();
return false;
}
change = ctx.getChange();
ChangeUpdate update = ctx.getUpdate(change.currentPatchSetId());
ChangeNotes notes = update.getNotes().load();
try {
ImmutableMap<String, String> existingCustomKeyedValues = notes.getCustomKeyedValues();
ImmutableMap<String, String> tryingToAdd = extractCustomKeyedValues(input.add);
ImmutableSet<String> tryingToRemove = extractCustomKeys(input.remove);
validationListeners.runEach(
l -> l.validateCustomKeyedValues(update.getChange(), tryingToAdd, tryingToRemove),
ValidationException.class);
Map<String, String> newValues = new HashMap<>(existingCustomKeyedValues);
Map<String, String> added = new HashMap<>();
// Do the removes before the additions so that adding a key with a value while
// removing the key consists of adding the key with that new value.
for (String key : tryingToRemove) {
if (!newValues.containsKey(key)) {
continue;
}
update.deleteCustomKeyedValue(key);
newValues.remove(key);
}
for (Map.Entry<String, String> add : tryingToAdd.entrySet()) {
if (newValues.containsKey(add.getKey())
&& newValues.get(add.getKey()).equals(add.getValue())) {
continue;
}
update.addCustomKeyedValue(add.getKey(), add.getValue());
newValues.put(add.getKey(), add.getValue());
added.put(add.getKey(), add.getValue());
}
if (newValues.size() > MAX_CUSTOM_KEYED_VALUES) {
throw new ValidationException("Too many custom keyed values.");
}
toAdd = ImmutableMap.copyOf(added);
toRemove =
ImmutableSet.copyOf(
Sets.filter(tryingToRemove, k -> existingCustomKeyedValues.containsKey(k)));
updatedCustomKeyedValues = ImmutableMap.copyOf(newValues);
return true;
} catch (ValidationException | InvalidCustomKeyedValueException e) {
throw new BadRequestException(e.getMessage(), e);
}
}
@Override
public void postUpdate(PostUpdateContext ctx) {
if (updated() && fireEvent) {
customKeyedValuesEdited.fire(
ctx.getChangeData(change),
ctx.getAccount(),
updatedCustomKeyedValues,
toAdd,
toRemove,
ctx.getWhen());
}
}
public ImmutableMap<String, String> getUpdatedCustomKeyedValues() {
checkState(
updatedCustomKeyedValues != null,
"getUpdatedCustomKeyedValues() only valid after executing op");
return updatedCustomKeyedValues;
}
private boolean updated() {
return (toAdd != null && !toAdd.isEmpty()) || (toRemove != null && !toRemove.isEmpty());
}
}