blob: 8c932fc2b1237139ce9c9643c80a4fbbf07b5f75 [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;
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Change;
import com.google.gerrit.server.update.BatchUpdateListener;
import java.io.IOException;
import java.util.Collection;
import java.util.Optional;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.transport.PushCertificate;
/**
* An interface for executing updates of multiple {@link ChangeDraftUpdate} instances.
*
* <p>Expected usage flow:
*
* <ol>
* <li>Inject an instance of {@link AbstractFactory}.
* <li>Create an instance of this interface using the factory.
* <li>Call ({@link #queueAllDraftUpdates} or {@link #queueDeletionForChangeDrafts} for all
* expected updates. The changes are marked to be executed either synchronously or
* asynchronously, based on {@link #canRunAsync}.
* <li>Call both {@link #executeAllSyncUpdates} and {@link #executeAllAsyncUpdates} methods.
* Running these methods with no pending updates is a no-op.
* </ol>
*/
public interface ChangeDraftUpdateExecutor {
interface AbstractFactory {
// Guice cannot bind either:
// - A parameterized entity.
// - A factory creating an interface (rather than a class).
// To overcome this - we declare the create method in this non-parameterized interface, then
// extend it with a factory returning an actual class.
ChangeDraftUpdateExecutor create(CurrentUser currentUser);
}
interface Factory<T extends ChangeDraftUpdateExecutor> extends AbstractFactory {
@Override
T create(CurrentUser currentUser);
}
/**
* Queues all provided updates for later execution.
*
* <p>The updates are queued to either run synchronously just after change repositories updates,
* or to run asynchronously afterwards, based on {@link #canRunAsync}.
*/
void queueAllDraftUpdates(ListMultimap<String, ChangeDraftUpdate> updates) throws IOException;
/**
* Extracts all drafts (of all authors) for the given change and queue their deletion.
*
* <p>See {@link #canRunAsync} for whether the deletions are scheduled as synchronous or
* asynchronous.
*/
void queueDeletionForChangeDrafts(Change.Id id) throws IOException;
/**
* Execute all previously queued sync updates.
*
* <p>NOTE that {@link BatchUpdateListener#beforeUpdateRefs} events are not fired by this method.
* post-update events can be fired by the caller only for implementations that return a valid
* {@link BatchRefUpdate}.
*
* @param dryRun whether this is a dry run - i.e. no updates should be made
* @param refLogIdent user to log as the update creator
* @param refLogMessage message to put in the updates log
* @return the executed update, if supported by the implementing class
* @throws IOException in case of an update failure.
*/
Optional<BatchRefUpdate> executeAllSyncUpdates(
boolean dryRun, @Nullable PersonIdent refLogIdent, @Nullable String refLogMessage)
throws IOException;
/**
* Execute all previously queued async updates.
*
* @param refLogIdent user to log as the update creator
* @param refLogMessage message to put in the updates log
* @param pushCert to use for the update
*/
void executeAllAsyncUpdates(
@Nullable PersonIdent refLogIdent,
@Nullable String refLogMessage,
@Nullable PushCertificate pushCert);
/** Returns whether any updates are queued. */
boolean isEmpty();
/** Returns the given updates that match the provided type. */
default <UpdateT extends ChangeDraftUpdate> ListMultimap<String, UpdateT> filterTypedUpdates(
ListMultimap<String, ChangeDraftUpdate> updates, Class<UpdateT> updateType) {
ListMultimap<String, UpdateT> res = MultimapBuilder.hashKeys().arrayListValues().build();
for (String key : updates.keySet()) {
res.putAll(
key,
updates.get(key).stream()
.map(u -> u.toOptionalChangeDraftUpdateSubtype(updateType))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(toImmutableList()));
}
return res;
}
/** Returns whether all provided updates can run asynchronously. */
default boolean canRunAsync(Collection<? extends ChangeDraftUpdate> updates) {
return updates.stream().allMatch(u -> u.canRunAsync());
}
}