/*
* JBoss, Home of Professional Open Source.
* Copyright 2011, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.test.capedwarf.datastore.test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Transaction;
import com.google.appengine.api.datastore.TransactionOptions;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.test.capedwarf.common.support.All;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* @author <a href="mailto:ales.justin@jboss.org">Ales Justin</a>
*/
@RunWith(Arquillian.class)
@Category(All.class)
public class TransactionsTest extends SimpleTestBase {
@Test
public void testBasicTxPut() throws Exception {
Entity entity = createTestEntity();
Transaction tx = service.beginTransaction();
try {
service.put(tx, entity);
assertStoreDoesNotContain(entity);
tx.commit();
assertStoreContains(entity);
} catch (Exception e) {
tx.rollback();
throw e;
}
}
@Test
public void testBasicNoTxPut() throws Exception {
Entity entity = createTestEntity("NO_TX_KIND", 1);
service.put(null, entity);
assertStoreContains(entity);
}
@Test
public void testBasicTxDelete() throws Exception {
Entity entity = createTestEntity();
service.put(entity);
Transaction tx = service.beginTransaction();
try {
service.delete(tx, entity.getKey());
assertStoreContains(entity);
tx.commit();
assertStoreDoesNotContain(entity);
} catch (Exception e) {
tx.rollback();
throw e;
}
}
@Test
public void testBasicNoTxDelete() throws Exception {
Entity entity = createTestEntity("NO_TX_KIND", 2);
service.put(null, entity);
assertStoreContains(entity);
service.delete(entity.getKey());
assertStoreDoesNotContain(entity);
}
@Test
public void testRollbackWhenPuttingEntity() throws Exception {
Entity entity = createTestEntity("ROLLBACK", 1);
Transaction tx = service.beginTransaction();
service.put(tx, entity);
tx.rollback();
// should not be there due to rollback
assertStoreDoesNotContain(entity);
}
@Test
public void testRollbackWhenModifyingEntity() throws Exception {
Entity entity = new Entity("test");
entity.setProperty("name", "original");
Key key = service.put(entity);
Transaction tx = service.beginTransaction();
Entity entity2 = service.get(key);
entity2.setProperty("name", "modified");
service.put(tx, entity2);
tx.rollback();
Entity entity3 = service.get(key);
assertEquals("original", entity3.getProperty("name"));
}
@Test
public void testRollbackWhenModifyingEntityObtainedThroughQuery() throws Exception {
assertRollbackSucceedsWhenResultFetchedWith(new ResultFetcher() {
public Entity fetchResult(PreparedQuery preparedQuery) {
return preparedQuery.asSingleEntity();
}
});
assertRollbackSucceedsWhenResultFetchedWith(new ResultFetcher() {
public Entity fetchResult(PreparedQuery preparedQuery) {
return preparedQuery.asIterator().next();
}
});
assertRollbackSucceedsWhenResultFetchedWith(new ResultFetcher() {
public Entity fetchResult(PreparedQuery preparedQuery) {
return preparedQuery.asIterable().iterator().next();
}
});
assertRollbackSucceedsWhenResultFetchedWith(new ResultFetcher() {
public Entity fetchResult(PreparedQuery preparedQuery) {
return preparedQuery.asList(withDefaults()).get(0);
}
});
assertRollbackSucceedsWhenResultFetchedWith(new ResultFetcher() {
public Entity fetchResult(PreparedQuery preparedQuery) {
return preparedQuery.asQueryResultIterator().next();
}
});
assertRollbackSucceedsWhenResultFetchedWith(new ResultFetcher() {
public Entity fetchResult(PreparedQuery preparedQuery) {
return preparedQuery.asQueryResultIterable().iterator().next();
}
});
assertRollbackSucceedsWhenResultFetchedWith(new ResultFetcher() {
public Entity fetchResult(PreparedQuery preparedQuery) {
return preparedQuery.asQueryResultList(withDefaults()).get(0);
}
});
}
private void assertRollbackSucceedsWhenResultFetchedWith(ResultFetcher resultFetcher) throws EntityNotFoundException {
Entity entity = new Entity("test");
entity.setProperty("name", "original");
Key key = service.put(entity);
try {
Transaction tx = service.beginTransaction();
PreparedQuery preparedQuery = service.prepare(new Query("test"));
Entity entity2 = resultFetcher.fetchResult(preparedQuery);
entity2.setProperty("name", "modified");
service.put(tx, entity2);
tx.rollback();
Entity entity3 = service.get(key);
assertEquals("original", entity3.getProperty("name"));
} finally {
service.delete(entity.getKey());
}
}
@Test
public void testNoIdKey() throws Exception {
Entity entity = new Entity("NO_ID");
Key key = service.put(entity);
assertTrue(key.isComplete());
}
@Test
public void testNested() throws Exception {
assertNoActiveTransactions();
Entity e1;
Entity e2;
Transaction t1 = service.beginTransaction();
try {
e1 = createTestEntity("DUMMY_a", 1);
service.put(t1, e1);
assertStoreDoesNotContain(e1);
assertActiveTransactions(t1);
Transaction t2 = service.beginTransaction();
try {
e2 = createTestEntity("DUMMY_b", 2);
service.put(e2);
assertActiveTransactions(t1, t2);
assertStoreDoesNotContain(e2);
} finally {
t2.rollback();
}
assertActiveTransactions(t1);
// assertStoreDoesNotContain(e2); // should not be there due to rollback
} finally {
t1.commit();
}
assertStoreContains(e1);
assertStoreDoesNotContain(e2); // should not be there due to rollback
assertNoActiveTransactions();
}
@Test
public void testMultipleEntityGroupsInSingleTransactionAreNotAllowed() {
Transaction tx = service.beginTransaction();
try {
Entity person = new Entity("Person", "tom");
service.put(person);
try {
Entity photoNotAChild = new Entity("Photo");
photoNotAChild.setProperty("photoUrl", "http://domain.com/path/to/photo.jpg");
service.put(photoNotAChild);
fail("put should have thrown IllegalArgumentException");
} catch (IllegalArgumentException ex) {
// pass
}
} finally {
tx.rollback();
}
}
@Test
public void testAncestorIsMandatoryInQueriesInsideTransaction() {
Transaction tx = service.beginTransaction();
try {
service.prepare(new Query("test")); // no tx, ancestor not necessary
service.prepare(null, new Query("test")); // no tx, ancestor not necessary
service.prepare(tx, new Query("test").setAncestor(KeyFactory.createKey("some_kind", "some_id"))); // tx + ancestor
try {
service.prepare(tx, new Query("test")); // tx, but no ancestor
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
// pass
}
} finally {
tx.rollback();
}
}
@Test
public void testGetWithDifferentAncestorsInsideSameTransactionAreNotAllowed() {
service.put(new Entity("foo", "1"));
service.put(new Entity("foo", "2"));
Transaction tx = service.beginTransaction();
try {
service.get(Arrays.asList(KeyFactory.createKey("foo", "1")));
try {
service.get(Arrays.asList(KeyFactory.createKey("foo", "2")));
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
// pass
}
} finally {
tx.rollback();
}
}
@Test
public void testMultipleQueriesWithSameAncestorInsideSameTransactionAreAllowed() {
Transaction tx = service.beginTransaction();
try {
Key ancestor = KeyFactory.createKey("ancestor", "1");
prepareQueryWithAncestor(tx, ancestor).asIterator().hasNext();
prepareQueryWithAncestor(tx, ancestor).asIterator().hasNext();
} finally {
tx.rollback();
}
}
@Test
public void testQueriesWithDifferentAncestorsInsideSameTransactionThrowIllegalArgumentException() {
Transaction tx = service.beginTransaction();
try {
Key someAncestor = KeyFactory.createKey("ancestor", "1");
prepareQueryWithAncestor(tx, someAncestor).asIterator().hasNext();
Key otherAncestor = KeyFactory.createKey("ancestor", "2");
assertIAEWhenAccessingResult(prepareQueryWithAncestor(tx, otherAncestor));
} finally {
tx.rollback();
}
}
@Test
public void testQueriesWithDifferentAncestorsInsideSameTransactionNoUsage() {
Transaction tx = service.beginTransaction();
try {
Key a1 = KeyFactory.createKey("ancestor", "1");
prepareQueryWithAncestor(tx, a1).asIterator();
Key a2 = KeyFactory.createKey("ancestor", "2");
prepareQueryWithAncestor(tx, a2).asList(FetchOptions.Builder.withDefaults());
Key a3 = KeyFactory.createKey("ancestor", "3");
prepareQueryWithAncestor(tx, a3).asIterable();
Key a4 = KeyFactory.createKey("ancestor", "4");
prepareQueryWithAncestor(tx, a4).asQueryResultIterable();
Key a5 = KeyFactory.createKey("ancestor", "5");
prepareQueryWithAncestor(tx, a5).asQueryResultIterator();
Key a6 = KeyFactory.createKey("ancestor", "6");
prepareQueryWithAncestor(tx, a6).asQueryResultList(FetchOptions.Builder.withDefaults());
} finally {
tx.rollback();
}
}
@Test
public void testXGTransaction() throws Exception {
if (isJBossImpl(service) == false)
return;
final int N = 25; // max XG entity groups
List<Key> keys = new ArrayList<Key>();
for (int i = 0; i < N + 1; i++) {
keys.add(service.put(new Entity("XG")));
}
boolean ok = false;
Transaction tx = service.beginTransaction(TransactionOptions.Builder.withXG(true));
try {
for (int i = 0; i < N; i++) {
service.get(keys.get(i));
}
try {
service.get(keys.get(N));
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
// pass
}
ok = true;
} finally {
if (ok) {
tx.commit();
} else {
tx.rollback();
}
}
}
@Test
public void testOperatingOnClosedTransaction() throws Exception {
Transaction tx = service.beginTransaction();
Entity entity = createTestEntity();
service.put(tx, entity);
tx.commit();
try {
service.get(tx, entity.getKey());
fail("Expected IllegalStateException");
} catch (IllegalStateException ok) {
}
try {
service.delete(tx, entity.getKey());
fail("Expected IllegalStateException");
} catch (IllegalStateException ok) {
}
try {
service.put(tx, entity);
fail("Expected IllegalStateException");
} catch (IllegalStateException ok) {
}
}
private PreparedQuery prepareQueryWithAncestor(Transaction tx, Key someAncestor) {
return service.prepare(tx, new Query("foo").setAncestor(someAncestor));
}
private void assertNoActiveTransactions() {
assertActiveTransactions();
}
protected void assertActiveTransactions(Transaction... txs) {
Collection<Transaction> transactions = service.getActiveTransactions();
assertNotNull(txs);
Set<Transaction> expected = new HashSet<Transaction>(transactions);
Set<Transaction> existing = new HashSet<Transaction>(Arrays.asList(txs));
assertEquals(expected, existing);
for (Transaction tx : txs) {
assertTrue(tx.isActive());
}
}
private interface ResultFetcher {
Entity fetchResult(PreparedQuery preparedQuery);
}
}