ReplicaPushRequest.java

/*
 * Copyright (C) 2016, Google Inc.
 * and other copyright owners as documented in the project's IP log.
 *
 * This program and the accompanying materials are made available
 * under the terms of the Eclipse Distribution License v1.0 which
 * accompanies this distribution, is reproduced below, and is
 * available at http://www.eclipse.org/org/documents/edl-v10.php
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials provided
 *   with the distribution.
 *
 * - Neither the name of the Eclipse Foundation, Inc. nor the
 *   names of its contributors may be used to endorse or promote
 *   products derived from this software without specific prior
 *   written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.eclipse.jgit.internal.ketch;

import java.util.Collection;
import java.util.Map;

import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.ReceiveCommand;

/**
 * A push request sending objects to a replica, and its result.
 * <p>
 * Implementors of {@link org.eclipse.jgit.internal.ketch.KetchReplica} must
 * populate the command result fields, {@link #setRefs(Map)}, and call one of
 * {@link #setException(Repository, Throwable)} or {@link #done(Repository)} to
 * finish processing.
 */
public class ReplicaPushRequest {
	private final KetchReplica replica;
	private final Collection<ReceiveCommand> commands;
	private Map<String, Ref> refs;
	private Throwable exception;
	private boolean notified;

	/**
	 * Construct a new push request for a replica.
	 *
	 * @param replica
	 *            the replica being pushed to.
	 * @param commands
	 *            commands to be executed.
	 */
	public ReplicaPushRequest(KetchReplica replica,
			Collection<ReceiveCommand> commands) {
		this.replica = replica;
		this.commands = commands;
	}

	/**
	 * Get commands to be executed, and their results.
	 *
	 * @return commands to be executed, and their results.
	 */
	public Collection<ReceiveCommand> getCommands() {
		return commands;
	}

	/**
	 * Get remote references, usually from the advertisement.
	 *
	 * @return remote references, usually from the advertisement.
	 */
	@Nullable
	public Map<String, Ref> getRefs() {
		return refs;
	}

	/**
	 * Set references observed from the replica.
	 *
	 * @param refs
	 *            references observed from the replica.
	 */
	public void setRefs(Map<String, Ref> refs) {
		this.refs = refs;
	}

	/**
	 * Get exception thrown, if any.
	 *
	 * @return exception thrown, if any.
	 */
	@Nullable
	public Throwable getException() {
		return exception;
	}

	/**
	 * Mark the request as crashing with a communication error.
	 * <p>
	 * This method may take significant time acquiring the leader lock and
	 * updating the Ketch state machine with the failure.
	 *
	 * @param repo
	 *            local repository reference used by the push attempt.
	 * @param err
	 *            exception thrown during communication.
	 */
	public void setException(@Nullable Repository repo, Throwable err) {
		if (KetchReplica.log.isErrorEnabled()) {
			KetchReplica.log.error(describe("failed"), err); //$NON-NLS-1$
		}
		if (!notified) {
			notified = true;
			exception = err;
			replica.afterPush(repo, this);
		}
	}

	/**
	 * Mark the request as completed without exception.
	 * <p>
	 * This method may take significant time acquiring the leader lock and
	 * updating the Ketch state machine with results from this replica.
	 *
	 * @param repo
	 *            local repository reference used by the push attempt.
	 */
	public void done(Repository repo) {
		if (KetchReplica.log.isDebugEnabled()) {
			KetchReplica.log.debug(describe("completed")); //$NON-NLS-1$
		}
		if (!notified) {
			notified = true;
			replica.afterPush(repo, this);
		}
	}

	private String describe(String heading) {
		StringBuilder b = new StringBuilder();
		b.append("push to "); //$NON-NLS-1$
		b.append(replica.describeForLog());
		b.append(' ').append(heading).append(":\n"); //$NON-NLS-1$
		for (ReceiveCommand cmd : commands) {
			b.append(String.format(
					"  %-12s %-12s %s %s", //$NON-NLS-1$
					LeaderSnapshot.str(cmd.getOldId()),
					LeaderSnapshot.str(cmd.getNewId()),
					cmd.getRefName(),
					cmd.getResult()));
			if (cmd.getMessage() != null) {
				b.append(' ').append(cmd.getMessage());
			}
			b.append('\n');
		}
		return b.toString();
	}
}