Line data Source code
1 : // Copyright (C) 2015 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.change; 16 : 17 : import static com.google.common.base.Preconditions.checkState; 18 : import static com.google.gerrit.server.change.HashtagsUtil.extractTags; 19 : 20 : import com.google.common.base.Joiner; 21 : import com.google.common.collect.ImmutableSortedSet; 22 : import com.google.common.collect.Ordering; 23 : import com.google.gerrit.common.Nullable; 24 : import com.google.gerrit.entities.Change; 25 : import com.google.gerrit.extensions.api.changes.HashtagsInput; 26 : import com.google.gerrit.extensions.restapi.AuthException; 27 : import com.google.gerrit.extensions.restapi.BadRequestException; 28 : import com.google.gerrit.extensions.restapi.MethodNotAllowedException; 29 : import com.google.gerrit.server.ChangeMessagesUtil; 30 : import com.google.gerrit.server.change.HashtagsUtil.InvalidHashtagException; 31 : import com.google.gerrit.server.extensions.events.HashtagsEdited; 32 : import com.google.gerrit.server.notedb.ChangeNotes; 33 : import com.google.gerrit.server.notedb.ChangeUpdate; 34 : import com.google.gerrit.server.plugincontext.PluginSetContext; 35 : import com.google.gerrit.server.update.BatchUpdateOp; 36 : import com.google.gerrit.server.update.ChangeContext; 37 : import com.google.gerrit.server.update.PostUpdateContext; 38 : import com.google.gerrit.server.validators.HashtagValidationListener; 39 : import com.google.gerrit.server.validators.ValidationException; 40 : import com.google.inject.Inject; 41 : import com.google.inject.assistedinject.Assisted; 42 : import java.io.IOException; 43 : import java.util.Collection; 44 : import java.util.HashSet; 45 : import java.util.Set; 46 : 47 : public class SetHashtagsOp implements BatchUpdateOp { 48 : public interface Factory { 49 : SetHashtagsOp create(HashtagsInput input); 50 : } 51 : 52 : private final ChangeMessagesUtil cmUtil; 53 : private final PluginSetContext<HashtagValidationListener> validationListeners; 54 : private final HashtagsEdited hashtagsEdited; 55 : private final HashtagsInput input; 56 : 57 8 : private boolean fireEvent = true; 58 : 59 : private Change change; 60 : private Set<String> toAdd; 61 : private Set<String> toRemove; 62 : private ImmutableSortedSet<String> updatedHashtags; 63 : 64 : @Inject 65 : SetHashtagsOp( 66 : ChangeMessagesUtil cmUtil, 67 : PluginSetContext<HashtagValidationListener> validationListeners, 68 : HashtagsEdited hashtagsEdited, 69 8 : @Assisted @Nullable HashtagsInput input) { 70 8 : this.cmUtil = cmUtil; 71 8 : this.validationListeners = validationListeners; 72 8 : this.hashtagsEdited = hashtagsEdited; 73 8 : this.input = input; 74 8 : } 75 : 76 : public SetHashtagsOp setFireEvent(boolean fireEvent) { 77 3 : this.fireEvent = fireEvent; 78 3 : return this; 79 : } 80 : 81 : @Override 82 : public boolean updateChange(ChangeContext ctx) 83 : throws AuthException, BadRequestException, MethodNotAllowedException, IOException { 84 8 : if (input == null || (input.add == null && input.remove == null)) { 85 0 : updatedHashtags = ImmutableSortedSet.of(); 86 0 : return false; 87 : } 88 : 89 8 : change = ctx.getChange(); 90 8 : ChangeUpdate update = ctx.getUpdate(change.currentPatchSetId()); 91 8 : ChangeNotes notes = update.getNotes().load(); 92 : 93 : try { 94 8 : Set<String> existingHashtags = notes.getHashtags(); 95 8 : Set<String> updated = new HashSet<>(); 96 8 : toAdd = new HashSet<>(extractTags(input.add)); 97 8 : toRemove = new HashSet<>(extractTags(input.remove)); 98 : 99 8 : validationListeners.runEach( 100 0 : l -> l.validateHashtags(update.getChange(), toAdd, toRemove), ValidationException.class); 101 8 : updated.addAll(existingHashtags); 102 8 : toAdd.removeAll(existingHashtags); 103 8 : toRemove.retainAll(existingHashtags); 104 8 : if (updated()) { 105 8 : updated.addAll(toAdd); 106 8 : updated.removeAll(toRemove); 107 8 : update.setHashtags(updated); 108 8 : addMessage(ctx); 109 : } 110 : 111 8 : updatedHashtags = ImmutableSortedSet.copyOf(updated); 112 8 : return true; 113 1 : } catch (ValidationException | InvalidHashtagException e) { 114 1 : throw new BadRequestException(e.getMessage(), e); 115 : } 116 : } 117 : 118 : private void addMessage(ChangeContext ctx) { 119 8 : StringBuilder msg = new StringBuilder(); 120 8 : appendHashtagMessage(msg, "added", toAdd); 121 8 : appendHashtagMessage(msg, "removed", toRemove); 122 8 : cmUtil.setChangeMessage(ctx, msg.toString(), ChangeMessagesUtil.TAG_SET_HASHTAGS); 123 8 : } 124 : 125 : private void appendHashtagMessage(StringBuilder b, String action, Set<String> hashtags) { 126 8 : if (isNullOrEmpty(hashtags)) { 127 8 : return; 128 : } 129 : 130 8 : if (b.length() > 0) { 131 1 : b.append("\n"); 132 : } 133 8 : b.append("Hashtag"); 134 8 : if (hashtags.size() > 1) { 135 8 : b.append("s"); 136 : } 137 8 : b.append(" "); 138 8 : b.append(action); 139 8 : b.append(": "); 140 8 : b.append(Joiner.on(", ").join(Ordering.natural().sortedCopy(hashtags))); 141 8 : } 142 : 143 : @Override 144 : public void postUpdate(PostUpdateContext ctx) { 145 8 : if (updated() && fireEvent) { 146 5 : hashtagsEdited.fire( 147 5 : ctx.getChangeData(change), 148 5 : ctx.getAccount(), 149 : updatedHashtags, 150 : toAdd, 151 : toRemove, 152 5 : ctx.getWhen()); 153 : } 154 8 : } 155 : 156 : public ImmutableSortedSet<String> getUpdatedHashtags() { 157 5 : checkState(updatedHashtags != null, "getUpdatedHashtags() only valid after executing op"); 158 5 : return updatedHashtags; 159 : } 160 : 161 : private boolean updated() { 162 8 : return !isNullOrEmpty(toAdd) || !isNullOrEmpty(toRemove); 163 : } 164 : 165 : private static boolean isNullOrEmpty(Collection<?> coll) { 166 8 : return coll == null || coll.isEmpty(); 167 : } 168 : }