summaryrefslogtreecommitdiff
path: root/src/test/java/com/amazon/carbonado
diff options
context:
space:
mode:
authorBrian S. O'Neill <bronee@gmail.com>2006-10-15 17:50:08 +0000
committerBrian S. O'Neill <bronee@gmail.com>2006-10-15 17:50:08 +0000
commit177735d070ba4b1e34e8f0e6fabfff0ab910995a (patch)
tree5edd4b160521bfc4ce11f10c174171ab7ead14bc /src/test/java/com/amazon/carbonado
parent12ef5759af48817825524724f4690962156c8179 (diff)
Created StorageCollection.
More tests added.
Diffstat (limited to 'src/test/java/com/amazon/carbonado')
-rw-r--r--src/test/java/com/amazon/carbonado/TestUtilities.java48
-rw-r--r--src/test/java/com/amazon/carbonado/repo/replicated/TestCursors.java204
-rw-r--r--src/test/java/com/amazon/carbonado/repo/replicated/TestProxiedStorable.java219
-rw-r--r--src/test/java/com/amazon/carbonado/repo/replicated/TestRepair.java294
-rw-r--r--src/test/java/com/amazon/carbonado/spi/StorableInterceptorFactory.java203
-rw-r--r--src/test/java/com/amazon/carbonado/stored/StorableTestBasicIdMunger.java42
6 files changed, 997 insertions, 13 deletions
diff --git a/src/test/java/com/amazon/carbonado/TestUtilities.java b/src/test/java/com/amazon/carbonado/TestUtilities.java
index 4132ac8..520dba7 100644
--- a/src/test/java/com/amazon/carbonado/TestUtilities.java
+++ b/src/test/java/com/amazon/carbonado/TestUtilities.java
@@ -82,6 +82,39 @@ public class TestUtilities {
}
public static Repository buildTempRepository(String name, int capacity, boolean isMaster) {
+ RepositoryBuilder builder = newTempRepositoryBuilder(name, capacity, isMaster);
+
+ try {
+ return builder.build();
+ } catch (RepositoryException e) {
+ throw new UnsupportedOperationException("Could not create repository", e);
+ }
+ }
+
+ public static RepositoryBuilder newTempRepositoryBuilder() {
+ return newTempRepositoryBuilder("test");
+ }
+
+ public static RepositoryBuilder newTempRepositoryBuilder(boolean isMaster) {
+ return newTempRepositoryBuilder("test", isMaster);
+ }
+
+ public static RepositoryBuilder newTempRepositoryBuilder(String name) {
+ return newTempRepositoryBuilder(name, DEFAULT_CAPACITY);
+ }
+
+ public static RepositoryBuilder newTempRepositoryBuilder(String name, boolean isMaster) {
+ return newTempRepositoryBuilder(name, DEFAULT_CAPACITY, isMaster);
+ }
+
+ public static RepositoryBuilder newTempRepositoryBuilder(String name, int capacity) {
+ return newTempRepositoryBuilder(name, capacity, true);
+ }
+
+ public static RepositoryBuilder newTempRepositoryBuilder(String name,
+ int capacity,
+ boolean isMaster)
+ {
BDBRepositoryBuilder builder = new BDBRepositoryBuilder();
builder.setProduct("JE");
builder.setName(name);
@@ -89,22 +122,11 @@ public class TestUtilities {
builder.setCacheSize(capacity);
builder.setLogInMemory(true);
builder.setMaster(isMaster);
+ builder.setEnvironmentHome(makeTestDirectoryString(name));
- if (sTempRepoDir == null) {
- sTempRepoDir = makeTestDirectoryString(name);
- }
-
- builder.setEnvironmentHome(sTempRepoDir);
-
- try {
- return builder.build();
- } catch (RepositoryException e) {
- throw new UnsupportedOperationException("Could not create repository", e);
- }
+ return builder;
}
- private static String sTempRepoDir;
-
private static String sAlphabet = "abcdefghijklmnopqrstuvwxyz";
private static Random sNumbers = new Random();
diff --git a/src/test/java/com/amazon/carbonado/repo/replicated/TestCursors.java b/src/test/java/com/amazon/carbonado/repo/replicated/TestCursors.java
new file mode 100644
index 0000000..d9a6b38
--- /dev/null
+++ b/src/test/java/com/amazon/carbonado/repo/replicated/TestCursors.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates. All rights reserved.
+ *
+ * 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.amazon.carbonado.repo.replicated;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import com.amazon.carbonado.Repository;
+import com.amazon.carbonado.RepositoryBuilder;
+import com.amazon.carbonado.Storage;
+import com.amazon.carbonado.Cursor;
+import com.amazon.carbonado.Query;
+import com.amazon.carbonado.FetchException;
+import com.amazon.carbonado.RepositoryException;
+import com.amazon.carbonado.ConfigurationException;
+
+import com.amazon.carbonado.TestUtilities;
+import com.amazon.carbonado.stored.StorableTestBasic;
+import com.amazon.carbonado.stored.STBContainer;
+
+/**
+ *
+ * @author Don Schneider
+ */
+public class TestCursors extends TestCase {
+
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(suite());
+ }
+
+ public static TestSuite suite() {
+ return new TestSuite(TestCursors.class);
+ }
+
+ public TestCursors(String s) {
+ super(s);
+ }
+
+ public TestCursors() {
+ this("default");
+ }
+
+ /**
+ * Test joins
+ */
+ public void test_joins() throws Exception {
+ Repository repository = TestUtilities.buildTempRepository("test_joins");
+ setup(repository);
+ do_test_joins(repository);
+ repository.close();
+ repository = null;
+ }
+
+ public void do_test_joins(Repository repository) throws Exception {
+ Storage<STBContainer> storage = repository.storageFor(STBContainer.class);
+ STBContainer s = storage.prepare();
+ s.setName("first");
+ s.setCategory("imaString_1");
+ s.setCount(1000001);
+
+ Collection<Integer> ids = checkContents(s, 1);
+
+ assertEquals(10, ids.size());
+
+ LinkedHashMap<String, Collection<Integer>> allIdCollections =
+ new LinkedHashMap<String, Collection<Integer>>();
+ Cursor<STBContainer> cSTBC = storage.query().fetch();
+ while (cSTBC.hasNext()) {
+ STBContainer stbc = cSTBC.next();
+ ids = checkContents(stbc, stbc.getCount());
+ for (Map.Entry entry : allIdCollections.entrySet()) {
+ if (entry.getKey().equals(stbc.getCategory())) {
+ assertTrue(ids.equals(entry.getValue()));
+ } else {
+ assertFalse(ids.equals(entry.getValue()));
+ }
+ }
+ allIdCollections.put(stbc.getCategory(), ids);
+ }
+ }
+
+ /**
+ * Test queries
+ */
+ public void test_queries() throws Exception {
+ Repository repository = TestUtilities.buildTempRepository("test_queries");
+ setup(repository);
+ do_test_queries(repository);
+ repository.close();
+ }
+
+ public void do_test_queries(Repository repository) throws Exception {
+ Storage<StorableTestBasic> storage =
+ repository.storageFor(StorableTestBasic.class);
+ filterTest(storage, "id > ? & id < ?", 10, 20, 11, 20);
+ filterTest(storage, "id >= ? & id < ?", 10, 20, 10, 20);
+ filterTest(storage, "id > ? & id <= ?", 10, 20, 11, 21);
+ filterTest(storage, "id >= ? & id <= ?", 10, 20, 10, 21);
+ }
+
+ /**
+ * Test RRQueries
+ * Includes an implicit test of the repository configuration
+ */
+ public void test_RRQueries() throws Exception {
+ RepositoryBuilder replicaRepository = TestUtilities.newTempRepositoryBuilder();
+ RepositoryBuilder masterRepository = TestUtilities.newTempRepositoryBuilder("alt");
+
+ ReplicatedRepositoryBuilder builder = new ReplicatedRepositoryBuilder();
+ try {
+ builder.assertReady();
+ fail("unready builder passed assertReady()");
+ }
+ catch (ConfigurationException e) {
+ // expected
+ }
+ builder.setName("test");
+ try {
+ builder.assertReady();
+ fail("unready builder passed assertReady()");
+ }
+ catch (ConfigurationException e) {
+ // expected
+ }
+ builder.setReplicaRepositoryBuilder(replicaRepository);
+ try {
+ builder.assertReady();
+ fail("builder config passed assertReady()");
+ }
+ catch (ConfigurationException e) {
+ // expected
+ }
+ builder.setMasterRepositoryBuilder(masterRepository);
+ builder.assertReady();
+
+ Repository RR = builder.build();
+
+ setup(RR);
+
+ do_test_joins(RR);
+ do_test_queries(RR);
+
+ RR.close();
+ }
+
+ private Collection<Integer> checkContents(STBContainer s, int count) throws FetchException {
+ Query<StorableTestBasic> q = s.getContained();
+ Cursor<StorableTestBasic> cSTB = q.fetch();
+ Collection<Integer> ids = new HashSet<Integer>(15);
+ while (cSTB.hasNext()) {
+ StorableTestBasic stb = cSTB.next();
+ assertEquals("imaString_" + count % 10, stb.getStringProp());
+ Integer id = new Integer(stb.getId());
+ assertFalse(ids.contains(id));
+ ids.add(id);
+ }
+ return ids;
+ }
+
+ private void filterTest(Storage<StorableTestBasic> storage,
+ String queryString,
+ int min,
+ int max,
+ int first,
+ int last)
+ throws Exception
+ {
+ Query<StorableTestBasic> q = storage.query(queryString).with(min).with(max);
+ Cursor<StorableTestBasic> c = q.fetch();
+ int i = first;
+ while (c.hasNext()) {
+ StorableTestBasic item = c.next();
+ assertEquals(queryString, i, item.getId());
+ i++;
+ }
+ assertEquals(queryString, last, i);
+ }
+
+ private void setup(Repository repository) throws RepositoryException {
+ StorableTestBasic.insertBunches(repository, 100);
+ STBContainer.insertBunches(repository, 30);
+ }
+}
diff --git a/src/test/java/com/amazon/carbonado/repo/replicated/TestProxiedStorable.java b/src/test/java/com/amazon/carbonado/repo/replicated/TestProxiedStorable.java
new file mode 100644
index 0000000..6de2faf
--- /dev/null
+++ b/src/test/java/com/amazon/carbonado/repo/replicated/TestProxiedStorable.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates. All rights reserved.
+ *
+ * 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.amazon.carbonado.repo.replicated;
+
+import java.lang.reflect.Method;
+
+import junit.framework.TestSuite;
+import com.amazon.carbonado.TestUtilities;
+import com.amazon.carbonado.spi.StorableInterceptorFactory;
+import com.amazon.carbonado.stored.StorableTestBasic;
+import com.amazon.carbonado.stored.StorableTestBasicIdMunger;
+import com.amazon.carbonado.TestStorables;
+
+import com.amazon.carbonado.Repository;
+import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.Storage;
+import com.amazon.carbonado.SupportException;
+import com.amazon.carbonado.FetchException;
+import com.amazon.carbonado.TestStorableBase;
+
+import com.amazon.carbonado.spi.WrappedStorage;
+
+/*
+ * TestProxiedStorable
+ *
+ * @author Don Schneider
+ */
+public class TestProxiedStorable extends TestStorableBase {
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(suite());
+ }
+
+ public static TestSuite suite() {
+ return new TestSuite(TestProxiedStorable.class);
+ }
+
+ public TestProxiedStorable() {
+ super();
+ }
+
+ /**
+ * Test copy storable properties interface
+ */
+ public void test_copyStorableProperties() throws Exception {
+ Storage<StorableTestBasic> storage = getRepository().storageFor(StorableTestBasic.class);
+ StorableTestBasic storable = storage.prepare();
+
+ TestStorables.InvocationTracker tracker = new TestStorables.InvocationTracker("tracker");
+ storable.copyAllProperties(tracker);
+ // unloaded, not dirty; nothing happens
+ tracker.assertTrack(0);
+
+ storable.setId(1);
+ storable.setIntProp(1);
+ storable.copyAllProperties(tracker);
+ // setId, setIntProp
+ tracker.assertTrack(0x1 + 0x10);
+ tracker.clearTracks();
+
+ setBasicProperties(storable);
+ storable.copyAllProperties(tracker);
+ // setXxx
+ tracker.assertTrack(TestStorables.ALL_SET_METHODS);
+ tracker.clearTracks();
+
+ storable = storage.prepare();
+ storable.copyPrimaryKeyProperties(tracker);
+ tracker.assertTrack(0);
+ setPrimaryKeyProperties(storable);
+ storable.copyPrimaryKeyProperties(tracker);
+ // setXxx
+ tracker.assertTrack(TestStorables.ALL_PRIMARY_KEYS);
+ tracker.clearTracks();
+
+ storable = storage.prepare();
+ storable.copyUnequalProperties(tracker);
+ // Better not do anything since unloaded
+ tracker.assertTrack(0);
+ storable.setIntProp(0); // this will now be dirty, and equal
+ storable.copyUnequalProperties(tracker);
+ // Should only examine one dirty property since still unloaded
+ tracker.assertTrack(0x8);
+ storable.setIntProp(1); // this will now be dirty and not equal
+ storable.copyUnequalProperties(tracker);
+ tracker.assertTrack(0x8 + 0x10);
+
+ // get a fresh one
+ storable = storage.prepare();
+ storable.setStringProp("hi");
+ storable.setId(22);
+ storable.copyPrimaryKeyProperties(tracker);
+ storable.copyDirtyProperties(tracker);
+ // setId, setStringProp
+ tracker.assertTrack(0x1 + 0x4);
+ }
+
+ /**
+ * Test Interceptor
+ */
+ public void test_Interceptor() throws Exception {
+ Storage<StorableTestBasic> storage = getRepository().storageFor(StorableTestBasic.class);
+ StorableTestBasic storable = storage.prepare();
+
+ StorableInterceptorFactory<StorableTestBasic> proxyFactory
+ = StorableInterceptorFactory.getInstance(StorableTestBasicIdMunger.class,
+ StorableTestBasic.class,
+ false);
+ StorableTestBasic proxy = proxyFactory.create(storable);
+
+ proxy.setId(1);
+ assertEquals(storable.getId(), 2<<8);
+
+ storable.setId(5<<8);
+ assertEquals(proxy.getId(), 4);
+
+ proxy.setStringProp("passthrough");
+ assertEquals("passthrough", storable.getStringProp());
+ }
+
+ /**
+ * Test replicatingStorable
+ */
+ public void test_replicatingStorable() throws Exception {
+ Repository altRepo = TestUtilities.buildTempRepository("alt");
+
+ final Storage<StorableTestBasic> readage =
+ getRepository().storageFor(StorableTestBasic.class);
+ final Storage<StorableTestBasic> writage = altRepo.storageFor(StorableTestBasic.class);
+
+ Storage<StorableTestBasic> wrappage =
+ new ReplicatedStorage<StorableTestBasic>(getRepository(), readage, writage);
+
+ StorableTestBasic replicator = wrappage.prepare();
+
+ replicator.setId(1);
+ setBasicProperties(replicator);
+ replicator.insert();
+
+ StorableTestBasic reader = load(readage, 1);
+ StorableTestBasic writer = load(writage, 1);
+
+ assertTrue(reader.equalProperties(writer));
+
+ assertStorableEquivalenceById(1, readage, writage);
+
+ replicator = wrappage.prepare();
+
+ replicator.setId(1);
+ replicator.setStringProp("updated");
+ replicator.setLongProp(2342332);
+ replicator.update();
+ writer = load(writage, 1);
+ reader = load(readage, 1);
+ assertTrue(reader.equalProperties(writer));
+
+ replicator.delete();
+
+
+ try {
+ reader.load();
+ fail("successfully loaded deleted 'read' storable");
+ }
+ catch (FetchException e) {
+ // expected
+ }
+ try {
+ writer.load();
+ fail("successfully loaded deleted 'write' storable");
+ }
+ catch (FetchException e) {
+ // expected
+ }
+
+ StorableTestBasic replicator2 = wrappage.prepare();
+
+ replicator2.setId(2);
+ setBasicProperties(replicator2);
+ replicator2.insert();
+
+ // Now use the old replicator (which should be in "unloaded" state, since we've
+ // just finished deleting something, to delete it.
+ replicator.setId(2);
+ replicator.delete();
+
+ try {
+ load(readage, 2);
+ fail("successfully loaded deleted 'read' storable 2");
+ }
+ catch (FetchException e) {
+ // expected
+ }
+ try {
+ load(writage, 2);
+ fail("successfully loaded deleted 'write' storable 2");
+ }
+ catch (FetchException e) {
+ // expected
+ }
+
+ altRepo.close();
+ altRepo = null;
+ }
+
+}
diff --git a/src/test/java/com/amazon/carbonado/repo/replicated/TestRepair.java b/src/test/java/com/amazon/carbonado/repo/replicated/TestRepair.java
new file mode 100644
index 0000000..d21fceb
--- /dev/null
+++ b/src/test/java/com/amazon/carbonado/repo/replicated/TestRepair.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates. All rights reserved.
+ *
+ * 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.amazon.carbonado.repo.replicated;
+
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import com.amazon.carbonado.OptimisticLockException;
+import com.amazon.carbonado.Repository;
+import com.amazon.carbonado.RepositoryBuilder;
+import com.amazon.carbonado.Storage;
+import com.amazon.carbonado.UniqueConstraintException;
+
+import com.amazon.carbonado.repo.replicated.ReplicatedRepository;
+import com.amazon.carbonado.repo.replicated.ReplicatedRepositoryBuilder;
+
+import com.amazon.carbonado.TestUtilities;
+import com.amazon.carbonado.stored.StorableTestBasic;
+import com.amazon.carbonado.stored.StorableVersioned;
+
+/**
+ *
+ *
+ * @author Brian S O'Neill
+ */
+public class TestRepair extends TestCase {
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(suite());
+ }
+
+ public static TestSuite suite() {
+ return new TestSuite(TestRepair.class);
+ }
+
+ private Repository mReplica;
+ private Repository mMaster;
+ private Repository mReplicated;
+
+ public TestRepair(String name) {
+ super(name);
+ }
+
+ protected void setUp() throws Exception {
+ RepositoryBuilder replica = TestUtilities.newTempRepositoryBuilder("rr-replica");
+ RepositoryBuilder master = TestUtilities.newTempRepositoryBuilder("rr-master");
+
+ ReplicatedRepositoryBuilder builder = new ReplicatedRepositoryBuilder();
+ builder.setName("rr");
+ builder.setReplicaRepositoryBuilder(replica);
+ builder.setMasterRepositoryBuilder(master);
+
+ ReplicatedRepository rr = (ReplicatedRepository) builder.build();
+
+ mReplica = rr.getReplicaRepository();
+ mMaster = rr.getMasterRepository();
+ mReplicated = rr;
+ }
+
+ protected void tearDown() throws Exception {
+ mReplicated.close();
+ mReplica = null;
+ mMaster = null;
+ mReplicated = null;
+ }
+
+ public void testMissingEntry() throws Exception {
+ // Insert an entry into master.
+ {
+ Storage<StorableTestBasic> storage = mMaster.storageFor(StorableTestBasic.class);
+ StorableTestBasic stb = storage.prepare();
+ stb.setId(5);
+ stb.setStringProp("hello");
+ stb.setIntProp(1);
+ stb.setLongProp(1L);
+ stb.setDoubleProp(1.0);
+ stb.insert();
+ }
+
+ // Verify not available from rr.
+ {
+ Storage<StorableTestBasic> storage = mReplicated.storageFor(StorableTestBasic.class);
+ StorableTestBasic stb = storage.prepare();
+ stb.setId(5);
+ assertFalse(stb.tryLoad());
+ }
+
+ // Insert into rr.
+ {
+ Storage<StorableTestBasic> storage = mReplicated.storageFor(StorableTestBasic.class);
+ StorableTestBasic stb = storage.prepare();
+ stb.setId(5);
+ stb.setStringProp("world");
+ stb.setIntProp(1);
+ stb.setLongProp(1L);
+ stb.setDoubleProp(1.0);
+ try {
+ stb.insert();
+ fail();
+ } catch (UniqueConstraintException e) {
+ }
+ }
+
+ // Wait a moment for repair thread to finish.
+ Thread.sleep(1000);
+
+ // Verify available from rr.
+ {
+ Storage<StorableTestBasic> storage = mReplicated.storageFor(StorableTestBasic.class);
+ StorableTestBasic stb = storage.prepare();
+ stb.setId(5);
+ assertTrue(stb.tryLoad());
+ assertEquals("hello", stb.getStringProp());
+ }
+ }
+
+ public void testMissingVersionedEntry() throws Exception {
+ // Insert an entry into master.
+ {
+ Storage<StorableVersioned> storage = mMaster.storageFor(StorableVersioned.class);
+ StorableVersioned sv = storage.prepare();
+ sv.setID(5);
+ sv.setValue("hello");
+ sv.insert();
+ }
+
+ // Verify not available from rr.
+ {
+ Storage<StorableVersioned> storage = mReplicated.storageFor(StorableVersioned.class);
+ StorableVersioned sv = storage.prepare();
+ sv.setID(5);
+ assertFalse(sv.tryLoad());
+ }
+
+ // Insert into rr.
+ {
+ Storage<StorableVersioned> storage = mReplicated.storageFor(StorableVersioned.class);
+ StorableVersioned sv = storage.prepare();
+ sv.setID(5);
+ sv.setValue("world");
+ try {
+ sv.insert();
+ fail();
+ } catch (UniqueConstraintException e) {
+ }
+ }
+
+ // Wait a moment for repair thread to finish.
+ Thread.sleep(1000);
+
+ // Verify available from rr.
+ {
+ Storage<StorableVersioned> storage = mReplicated.storageFor(StorableVersioned.class);
+ StorableVersioned sv = storage.prepare();
+ sv.setID(5);
+ assertTrue(sv.tryLoad());
+ assertEquals("hello", sv.getValue());
+ assertEquals(1, sv.getVersion());
+ }
+ }
+
+ public void testStaleEntry() throws Exception {
+ // Insert an entry into rr.
+ {
+ Storage<StorableVersioned> storage = mReplicated.storageFor(StorableVersioned.class);
+ StorableVersioned sv = storage.prepare();
+ sv.setID(5);
+ sv.setValue("hello");
+ sv.insert();
+ }
+
+ // Update master entry.
+ {
+ Storage<StorableVersioned> storage = mMaster.storageFor(StorableVersioned.class);
+ StorableVersioned sv = storage.prepare();
+ sv.setID(5);
+ sv.load();
+ sv.setValue("world");
+ sv.update();
+ }
+
+ // Verify old version in replica.
+ {
+ Storage<StorableVersioned> storage = mReplica.storageFor(StorableVersioned.class);
+ StorableVersioned sv = storage.prepare();
+ sv.setID(5);
+ sv.load();
+ assertEquals(1, sv.getVersion());
+ assertEquals("hello", sv.getValue());
+ }
+
+ // Attempt to update rr entry.
+ {
+ Storage<StorableVersioned> storage = mReplicated.storageFor(StorableVersioned.class);
+ StorableVersioned sv = storage.prepare();
+ sv.setID(5);
+ sv.load();
+ assertEquals(1, sv.getVersion());
+ assertEquals("hello", sv.getValue());
+
+ sv.setValue("ciao");
+ try {
+ sv.update();
+ fail();
+ } catch (OptimisticLockException e) {
+ }
+ }
+
+ // Wait a moment for repair thread to finish.
+ Thread.sleep(1000);
+
+ // Verify new version in rr and update it.
+ {
+ Storage<StorableVersioned> storage = mReplicated.storageFor(StorableVersioned.class);
+ StorableVersioned sv = storage.prepare();
+ sv.setID(5);
+ sv.load();
+ assertEquals(2, sv.getVersion());
+ assertEquals("world", sv.getValue());
+
+ sv.setValue("ciao");
+ sv.update();
+ }
+ }
+
+ public void testStaleEntryAndBackoff() throws Exception {
+ // Insert an entry into rr.
+ {
+ Storage<StorableVersioned> storage = mReplicated.storageFor(StorableVersioned.class);
+ StorableVersioned sv = storage.prepare();
+ sv.setID(5);
+ sv.setValue("hello");
+ sv.insert();
+ }
+
+ // Update master entry.
+ {
+ Storage<StorableVersioned> storage = mMaster.storageFor(StorableVersioned.class);
+ StorableVersioned sv = storage.prepare();
+ sv.setID(5);
+ sv.load();
+ sv.setValue("world");
+ sv.update();
+ }
+
+ // Attempt to update rr entry.
+ {
+ Storage<StorableVersioned> storage = mReplicated.storageFor(StorableVersioned.class);
+ StorableVersioned sv = storage.prepare();
+ sv.setID(5);
+
+ int failCount = 0;
+
+ for (int retryCount = 3;;) {
+ try {
+ sv.load();
+ sv.setValue("ciao");
+ sv.update();
+ break;
+ } catch (OptimisticLockException e) {
+ failCount++;
+ retryCount = e.backoff(e, retryCount, 1000);
+ }
+ }
+
+ assertTrue(failCount > 0);
+ }
+
+ // Verify new version in rr.
+ {
+ Storage<StorableVersioned> storage = mReplicated.storageFor(StorableVersioned.class);
+ StorableVersioned sv = storage.prepare();
+ sv.setID(5);
+ sv.load();
+ assertEquals(3, sv.getVersion());
+ assertEquals("ciao", sv.getValue());
+ }
+ }
+}
diff --git a/src/test/java/com/amazon/carbonado/spi/StorableInterceptorFactory.java b/src/test/java/com/amazon/carbonado/spi/StorableInterceptorFactory.java
new file mode 100644
index 0000000..fd4c715
--- /dev/null
+++ b/src/test/java/com/amazon/carbonado/spi/StorableInterceptorFactory.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates. All rights reserved.
+ *
+ * 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.amazon.carbonado.spi;
+
+import java.util.Map;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.ref.Reference;
+import java.lang.ref.SoftReference;
+
+import org.cojen.util.ClassInjector;
+import org.cojen.util.WeakIdentityMap;
+import org.cojen.classfile.TypeDesc;
+import org.cojen.classfile.ClassFile;
+import org.cojen.classfile.Modifiers;
+import org.cojen.classfile.MethodInfo;
+import org.cojen.classfile.CodeBuilder;
+
+import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.spi.CodeBuilderUtil;
+
+/**
+ * StorableInterceptorFactory creates instances of Storables that delegate
+ * calls to a proxy.
+ *
+ * <p>If the base class for the interceptor is abstract and has any methods implemented, those
+ * methods will be invoked directly and no further action taken.
+ *
+ * <p>Any methods which are not implemented will be delegated to the proxy which is provided to the
+ * constructor.
+ *
+ * @author Don Schneider
+ */
+public class StorableInterceptorFactory<S extends Storable> {
+
+ public static final String PROXY = "mProxy$";
+
+ private static Map<Object, Reference<StorableInterceptorFactory>>
+ cCache = new WeakIdentityMap();
+
+ /**
+ * @param interceptorType the handler type to be invoked for accessors and
+ * mutators, which should just be the type of S.
+ */
+ public static <S extends Storable> StorableInterceptorFactory<S> getInstance(
+ Class<? extends S> interceptorType, Class<S> userType, boolean shortCircuit)
+ {
+ synchronized (cCache) {
+ StorableInterceptorFactory<S> factory;
+ String key = interceptorType.getName() + userType.getName() + (shortCircuit?"S":"P");
+ Reference<StorableInterceptorFactory> ref = cCache.get(key);
+ if (null != ref) {
+ factory = ref.get();
+ if (factory != null) {
+ return factory;
+ }
+ }
+ factory = new StorableInterceptorFactory<S>(interceptorType, userType, shortCircuit);
+ cCache.put(key, new SoftReference<StorableInterceptorFactory>(factory));
+ return factory;
+ }
+ }
+
+ private final Constructor<? extends S> mConstructor;
+
+ private StorableInterceptorFactory(final Class<? extends S> interceptorType,
+ Class<S> userType,
+ boolean doShortCircuit) {
+ Class storableClass = generateStorable(interceptorType, userType, doShortCircuit);
+ try {
+ mConstructor = storableClass.getConstructor(userType);
+ }
+ catch (NoSuchMethodException e) {
+ throw new UndeclaredThrowableException(e);
+ }
+ }
+
+ private static <T extends Storable> Class<? extends T>
+ generateStorable(Class<? extends T> interceptorType,
+ Class<T> userType,
+ boolean doShortCircuit)
+ {
+ TypeDesc interceptorTypeDesc = TypeDesc.forClass(interceptorType);
+ TypeDesc userTypeDesc = TypeDesc.forClass(userType);
+
+ ClassInjector ci = ClassInjector.create(interceptorType.getName(), null);
+ ClassFile cf = CodeBuilderUtil.createStorableClassFile(
+ ci,
+ interceptorType,
+ false,
+ StorableInterceptorFactory.class.getName());
+
+ // private final Storable mProxy$;
+ cf.addField(Modifiers.PRIVATE.toFinal(true), PROXY, userTypeDesc);
+
+ final TypeDesc[] ctorParams = {userTypeDesc};
+ // Add public constructor:
+ {
+ final int storableHandler = 0;
+ MethodInfo mi = cf.addConstructor(Modifiers.PUBLIC, ctorParams);
+ CodeBuilder b = new CodeBuilder(mi);
+ b.loadThis();
+ try {
+ interceptorType.getConstructor(new Class[] {userType});
+ b.loadLocal(b.getParameter(storableHandler));
+ b.invokeSuperConstructor(ctorParams);
+ }
+ catch (NoSuchMethodException e) {
+ b.invokeSuperConstructor(null);
+ }
+
+
+ //// this.storableHandler = storableHandler
+ CodeBuilderUtil.assertParameterNotNull(b, storableHandler);
+ b.loadThis();
+ b.loadLocal(b.getParameter(storableHandler));
+ b.storeField(PROXY, userTypeDesc);
+
+ b.returnVoid();
+ }
+
+
+ // Add delegation for all abstract methods. It is the responsibility of the implementor
+ // to delegate the non-abstract methods
+
+ for (Method method : interceptorType.getMethods()) {
+ if (Modifier.isAbstract(method.getModifiers())) {
+ MethodInfo mi = cf.addMethod(method);
+ CodeBuilder b = new CodeBuilder(mi);
+
+ // If we're asked to short circuit, we don't bother proxying. This is useful
+ // for creating a "visitor" -- that is, only implement a few "set" methods
+ if (!doShortCircuit) {
+ b.loadThis();
+ b.loadField(PROXY, userTypeDesc);
+ for (int i = 0; i < method.getParameterTypes().length; i++) {
+ b.loadLocal(b.getParameter(i));
+ }
+ b.invoke(method);
+ }
+
+ if (void.class == method.getReturnType()) {
+ b.returnVoid();
+ } else {
+ b.returnValue(TypeDesc.forClass(method.getReturnType()));
+ }
+ }
+ }
+
+ Class result = ci.defineClass(cf);
+ return (Class<? extends T>) result;
+ }
+
+ /**
+ * Create a new proxied storable instance which delegates to the given
+ * proxies. All methods are fully delegated.
+ *
+ * @param storable to use as a proxy
+ */
+ public S create(Storable storable) {
+ try {
+ return mConstructor.newInstance(storable);
+ }
+ catch (InstantiationException e) {
+ InternalError error = new InternalError();
+ error.initCause(e);
+ throw error;
+ } catch (IllegalAccessException e) {
+ InternalError error = new InternalError();
+ error.initCause(e);
+ throw error;
+ } catch (InvocationTargetException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof RuntimeException) {
+ throw (RuntimeException) cause;
+ }
+ if (cause instanceof Error) {
+ throw (Error) cause;
+ }
+ InternalError error = new InternalError();
+ error.initCause(cause == null ? e : cause);
+ throw error;
+ }
+ }
+}
diff --git a/src/test/java/com/amazon/carbonado/stored/StorableTestBasicIdMunger.java b/src/test/java/com/amazon/carbonado/stored/StorableTestBasicIdMunger.java
new file mode 100644
index 0000000..1a1857a
--- /dev/null
+++ b/src/test/java/com/amazon/carbonado/stored/StorableTestBasicIdMunger.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates. All rights reserved.
+ *
+ * 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.amazon.carbonado.stored;
+
+import com.amazon.carbonado.PrimaryKey;
+
+/*
+ * StorableTestBasicIdMunger
+ *
+ * @author Don Schneider
+ */
+public abstract class StorableTestBasicIdMunger extends StorableTestBasic {
+ StorableTestBasic mProxy;
+
+ public StorableTestBasicIdMunger(StorableTestBasic proxy) {
+ mProxy = proxy;
+ }
+
+ public void setId(int id) {
+ mProxy.setId((id+1) << 8);
+ }
+
+ public int getId() {
+ return (mProxy.getId() >> 8) - 1;
+ }
+}