/* 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: * Gabriel Roldan (Boundless) - initial implementation */ package org.locationtech.geogig.geotools.data; import java.io.IOException; import java.util.Iterator; import javax.annotation.Nullable; import org.geotools.data.Transaction; import org.geotools.data.Transaction.State; import org.geotools.data.store.ContentEntry; import org.locationtech.geogig.api.Context; import org.locationtech.geogig.api.GeogigTransaction; import org.locationtech.geogig.api.plumbing.DiffIndex; import org.locationtech.geogig.api.plumbing.TransactionBegin; import org.locationtech.geogig.api.plumbing.diff.DiffEntry; import org.locationtech.geogig.api.porcelain.AddOp; import org.locationtech.geogig.api.porcelain.CheckoutOp; import org.locationtech.geogig.api.porcelain.CommitOp; import org.locationtech.geogig.api.porcelain.ConflictsException; import org.locationtech.geogig.api.porcelain.NothingToCommitException; import com.google.common.base.Optional; import com.google.common.base.Preconditions; /** * */ class GeogigTransactionState implements State { /** VERSIONING_COMMIT_AUTHOR */ static final String VERSIONING_COMMIT_AUTHOR = "VersioningCommitAuthor"; /** VERSIONING_COMMIT_MESSAGE */ static final String VERSIONING_COMMIT_MESSAGE = "VersioningCommitMessage"; private ContentEntry entry; private GeogigTransaction geogigTx; private Transaction tx; /** * @param entry */ public GeogigTransactionState(ContentEntry entry) { this.entry = entry; } public Optional<GeogigTransaction> getGeogigTransaction() { return Optional.fromNullable(this.geogigTx); } @Override public void setTransaction(@Nullable final Transaction transaction) { Preconditions.checkArgument(!Transaction.AUTO_COMMIT.equals(transaction)); if (transaction != null && this.tx != null) { throw new IllegalStateException( "New transaction set without closing old transaction first."); } this.tx = transaction; if (transaction == null) { // Transaction.removeState has been called (during // transaction.close()) if (this.geogigTx != null) { // throw new // IllegalStateException("Transaction is attempting to " // + "close a non committed or aborted geogig transaction"); geogigTx.abort(); } this.geogigTx = null; } else { if (this.geogigTx != null) { geogigTx.abort(); } GeoGigDataStore dataStore = (GeoGigDataStore) entry.getDataStore(); Context commandLocator = dataStore.getCommandLocator(this.tx); this.geogigTx = commandLocator.command(TransactionBegin.class).call(); // checkout the working branch final String workingBranch = dataStore.getOrFigureOutBranch(); this.geogigTx.command(CheckoutOp.class).setForce(true).setSource(workingBranch).call(); } } @Override public void addAuthorization(String AuthID) throws IOException { // not required } @Override public void commit() throws IOException { Preconditions.checkState(this.geogigTx != null); /* * This follows suite with the hack set on GeoSever's * org.geoserver.wfs.Transaction.getDatastoreTransaction() */ final Optional<String> txUserName = getTransactionProperty(VERSIONING_COMMIT_AUTHOR); final Optional<String> fullName = getTransactionProperty("fullname"); final Optional<String> email = getTransactionProperty("email"); final String author = fullName.isPresent() ? fullName.get() : txUserName.orNull(); String commitMessage = getTransactionProperty(VERSIONING_COMMIT_MESSAGE).orNull(); this.geogigTx.command(AddOp.class).call(); try { CommitOp commitOp = this.geogigTx.command(CommitOp.class); if (txUserName != null) { commitOp.setAuthor(author, email.orNull()); } if (commitMessage == null) { commitMessage = composeDefaultCommitMessage(); } commitOp.setMessage(commitMessage); commitOp.call(); } catch (NothingToCommitException nochanges) { // ok } try { this.geogigTx.setAuthor(author, email.orNull()).commit(); } catch (ConflictsException e) { // TODO: how should this be handled? this.geogigTx.abort(); } this.geogigTx = null; } private Optional<String> getTransactionProperty(final String propName) { Object property = this.tx.getProperty(propName); if (property instanceof String) { return Optional.of((String) property); } return Optional.absent(); } private String composeDefaultCommitMessage() { Iterator<DiffEntry> indexDiffs = this.geogigTx.command(DiffIndex.class).call(); int added = 0, removed = 0, modified = 0; StringBuilder msg = new StringBuilder(); while (indexDiffs.hasNext()) { DiffEntry entry = indexDiffs.next(); switch (entry.changeType()) { case ADDED: added++; break; case MODIFIED: modified++; break; case REMOVED: removed++; break; } if ((added + removed + modified) < 10) { msg.append("\n ").append(entry.changeType().toString().toLowerCase()).append(' ') .append(entry.newPath() == null ? entry.oldName() : entry.newPath()); } } int count = added + removed + modified; if (count > 10) { msg.append("\n And ").append(count - 10).append(" more changes."); } StringBuilder title = new StringBuilder(); if (added > 0) { title.append("added ").append(added); } if (modified > 0) { if (title.length() > 0) { title.append(", "); } title.append("modified ").append(modified); } if (removed > 0) { if (title.length() > 0) { title.append(", "); } title.append("removed ").append(removed); } if (count > 0) { title.append(" features via unversioned legacy client.\n"); } msg.insert(0, title); return msg.toString(); } @Override public void rollback() throws IOException { Preconditions.checkState(this.geogigTx != null); this.geogigTx.abort(); this.geogigTx = null; } }