/* Copyright (c) 2013-2014 Boundless and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Distribution License v1.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/org/documents/edl-v10.html * * Contributors: * Johnathan Garrett (LMN Solutions) - initial implementation */ package org.locationtech.geogig.api; import java.util.HashSet; import java.util.Set; import java.util.UUID; import javax.annotation.Nullable; import org.locationtech.geogig.api.plumbing.RefParse; import org.locationtech.geogig.api.plumbing.TransactionEnd; import org.locationtech.geogig.api.porcelain.ConflictsException; import org.locationtech.geogig.di.PluginDefaults; import org.locationtech.geogig.repository.Index; import org.locationtech.geogig.repository.Repository; import org.locationtech.geogig.repository.StagingArea; import org.locationtech.geogig.repository.WorkingTree; import org.locationtech.geogig.storage.ConfigDatabase; import org.locationtech.geogig.storage.DeduplicationService; import org.locationtech.geogig.storage.GraphDatabase; import org.locationtech.geogig.storage.ObjectDatabase; import org.locationtech.geogig.storage.RefDatabase; import org.locationtech.geogig.storage.StagingDatabase; import org.locationtech.geogig.storage.TransactionRefDatabase; import org.locationtech.geogig.storage.TransactionStagingArea; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; /** * Provides a method of performing concurrent operations on a single Geogig repository. * * @see org.locationtech.geogig.api.plumbing.TransactionBegin * @see org.locationtech.geogig.api.plumbing.TransactionEnd */ public class GeogigTransaction implements Context { private UUID transactionId; private Context injector; private final StagingArea transactionIndex; private final WorkingTree transactionWorkTree; private final TransactionRefDatabase transactionRefDatabase; private Optional<String> authorName = Optional.absent(); private Optional<String> authorEmail = Optional.absent(); /** * Constructs the transaction with the given ID and Injector. * * @param locator the non transactional command locator * @param transactionId the id of the transaction */ public GeogigTransaction(Context locator, UUID transactionId) { Preconditions.checkArgument(!(locator instanceof GeogigTransaction)); this.injector = locator; this.transactionId = transactionId; transactionIndex = new TransactionStagingArea(new Index(this), transactionId); transactionWorkTree = new WorkingTree(this); transactionRefDatabase = new TransactionRefDatabase(locator.refDatabase(), transactionId); } public void create() { transactionRefDatabase.create(); } public void close() { transactionRefDatabase.close(); } /** * * @param authorName name of the author of this transaction * @param authorEmail email of the author of this transaction * @return {@code this} */ public GeogigTransaction setAuthor(@Nullable String authorName, @Nullable String authorEmail) { this.authorName = Optional.fromNullable(authorName); this.authorEmail = Optional.fromNullable(authorEmail); return this; } /** * @return the transaction id of the transaction */ public UUID getTransactionId() { return transactionId; } @Override public WorkingTree workingTree() { return transactionWorkTree; } @Override public StagingArea index() { return transactionIndex; } @Override public RefDatabase refDatabase() { return transactionRefDatabase; } /** * Finds and returns an instance of a command of the specified class. * * @param commandClass the kind of command to locate and instantiate * @return a new instance of the requested command class, with its dependencies resolved */ @Override public <T extends AbstractGeoGigOp<?>> T command(Class<T> commandClass) { T instance = injector.command(commandClass); instance.setContext(this); return instance; } @Override public String toString() { return new StringBuilder(getClass().getSimpleName()).append('[').append(transactionId) .append(']').toString(); } public void commit() throws ConflictsException { injector.command(TransactionEnd.class).setAuthor(authorName.orNull(), authorEmail.orNull()) .setTransaction(this).setCancel(false).setRebase(true).call(); } public void commitSyncTransaction() throws ConflictsException { injector.command(TransactionEnd.class).setAuthor(authorName.orNull(), authorEmail.orNull()) .setTransaction(this).setCancel(false).call(); } public void abort() { injector.command(TransactionEnd.class).setTransaction(this).setCancel(true).call(); } @Override public Platform platform() { return injector.platform(); } @Override public ObjectDatabase objectDatabase() { return injector.objectDatabase(); } @Override public StagingDatabase stagingDatabase() { return transactionIndex != null ? transactionIndex.getDatabase() : injector .stagingDatabase(); } @Override public ConfigDatabase configDatabase() { return injector.configDatabase(); } @Override public GraphDatabase graphDatabase() { return injector.graphDatabase(); } @Override public Repository repository() { return injector.repository(); } @Override public DeduplicationService deduplicationService() { return injector.deduplicationService(); } @Override public PluginDefaults pluginDefaults() { return injector.pluginDefaults(); } /** * The set of refs that have either changed since, or didn't exist at, the time the transaction * was created. */ public ImmutableSet<Ref> getChangedRefs() { Set<String> changedRefNames = transactionRefDatabase.getChangedRefs(); Set<Ref> changedRefs = new HashSet<Ref>(); for (String name : changedRefNames) { Ref ref = this.command(RefParse.class).setName(name).call().get(); changedRefs.add(ref); } return ImmutableSet.copyOf(changedRefs); } }