summaryrefslogtreecommitdiff
path: root/src/main/java/com/amazon/carbonado/spi
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/amazon/carbonado/spi')
-rw-r--r--src/main/java/com/amazon/carbonado/spi/AbstractRepository.java368
-rw-r--r--src/main/java/com/amazon/carbonado/spi/AbstractSequenceValueProducer.java79
-rw-r--r--src/main/java/com/amazon/carbonado/spi/BelatedStorageCreator.java4
-rw-r--r--src/main/java/com/amazon/carbonado/spi/CodeBuilderUtil.java172
-rw-r--r--src/main/java/com/amazon/carbonado/spi/LobEngine.java31
-rw-r--r--src/main/java/com/amazon/carbonado/spi/MasterStorableGenerator.java57
-rw-r--r--src/main/java/com/amazon/carbonado/spi/MasterSupport.java1
-rw-r--r--src/main/java/com/amazon/carbonado/spi/SequenceValueGenerator.java290
-rw-r--r--src/main/java/com/amazon/carbonado/spi/SequenceValueProducer.java87
-rw-r--r--src/main/java/com/amazon/carbonado/spi/StorableGenerator.java208
-rw-r--r--src/main/java/com/amazon/carbonado/spi/StorableIndexSet.java20
-rw-r--r--src/main/java/com/amazon/carbonado/spi/StorageCollection.java128
-rw-r--r--src/main/java/com/amazon/carbonado/spi/StoragePool.java59
-rw-r--r--src/main/java/com/amazon/carbonado/spi/StoredLob.java3
-rw-r--r--src/main/java/com/amazon/carbonado/spi/WrappedStorage.java4
15 files changed, 827 insertions, 684 deletions
diff --git a/src/main/java/com/amazon/carbonado/spi/AbstractRepository.java b/src/main/java/com/amazon/carbonado/spi/AbstractRepository.java
new file mode 100644
index 0000000..d1c1843
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/spi/AbstractRepository.java
@@ -0,0 +1,368 @@
+/*
+ * 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.lang.ref.WeakReference;
+
+import java.util.Collection;
+import java.util.Map;
+
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.apache.commons.logging.Log;
+
+import org.cojen.util.WeakIdentityMap;
+
+import com.amazon.carbonado.IsolationLevel;
+import com.amazon.carbonado.Repository;
+import com.amazon.carbonado.RepositoryException;
+import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.Storage;
+import com.amazon.carbonado.SupportException;
+import com.amazon.carbonado.Transaction;
+
+import com.amazon.carbonado.capability.Capability;
+import com.amazon.carbonado.capability.ShutdownCapability;
+
+import com.amazon.carbonado.sequence.SequenceCapability;
+import com.amazon.carbonado.sequence.SequenceValueProducer;
+import com.amazon.carbonado.sequence.SequenceValueProducerPool;
+
+/**
+ * Implements basic functionality required by a core Repository.
+ *
+ * @param <Txn> Transaction type
+ * @author Brian S O'Neill
+ */
+public abstract class AbstractRepository<Txn>
+ implements Repository, ShutdownCapability, SequenceCapability
+{
+ private final String mName;
+ private final ReadWriteLock mShutdownLock;
+
+ private final ThreadLocal<TransactionManager<Txn>> mCurrentTxnMgr;
+
+ // Weakly tracks all TransactionManager instances for shutdown hook.
+ private final Map<TransactionManager<Txn>, ?> mAllTxnMgrs;
+
+ private final StoragePool mStoragePool;
+
+ private final SequenceValueProducerPool mSequencePool;
+
+ private ShutdownHook mShutdownHook;
+ volatile boolean mHasShutdown;
+
+ protected AbstractRepository(String name) {
+ if (name == null) {
+ throw new IllegalArgumentException("Repository name cannot be null");
+ }
+ mName = name;
+ mShutdownLock = new ReentrantReadWriteLock();
+ mCurrentTxnMgr = new ThreadLocal<TransactionManager<Txn>>();
+ mAllTxnMgrs = new WeakIdentityMap();
+
+ mStoragePool = new StoragePool() {
+ protected <S extends Storable> Storage<S> createStorage(Class<S> type)
+ throws RepositoryException
+ {
+ AbstractRepository.this.lockoutShutdown();
+ try {
+ return AbstractRepository.this.createStorage(type);
+ } finally {
+ AbstractRepository.this.unlockoutShutdown();
+ }
+ }
+ };
+
+ mSequencePool = new SequenceValueProducerPool() {
+ protected SequenceValueProducer createSequenceValueProducer(String name)
+ throws RepositoryException
+ {
+ AbstractRepository.this.lockoutShutdown();
+ try {
+ return AbstractRepository.this.createSequenceValueProducer(name);
+ } finally {
+ AbstractRepository.this.unlockoutShutdown();
+ }
+ }
+ };
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public <S extends Storable> Storage<S> storageFor(Class<S> type)
+ throws SupportException, RepositoryException
+ {
+ return mStoragePool.get(type);
+ }
+
+ public Transaction enterTransaction() {
+ return localTransactionManager().enter(null);
+ }
+
+ public Transaction enterTransaction(IsolationLevel level) {
+ return localTransactionManager().enter(level);
+ }
+
+ public Transaction enterTopTransaction(IsolationLevel level) {
+ return localTransactionManager().enterTop(level);
+ }
+
+ public IsolationLevel getTransactionIsolationLevel() {
+ return localTransactionManager().getIsolationLevel();
+ }
+
+ /**
+ * Default implementation checks if Repository implements Capability
+ * interface, and if so, returns the Repository.
+ */
+ @SuppressWarnings("unchecked")
+ public <C extends Capability> C getCapability(Class<C> capabilityType) {
+ if (capabilityType.isInstance(this)) {
+ return (C) this;
+ }
+ return null;
+ }
+
+ public void close() {
+ shutdown(false);
+ }
+
+ // Required by ShutdownCapability.
+ public boolean isAutoShutdownEnabled() {
+ return mShutdownHook != null;
+ }
+
+ // Required by ShutdownCapability.
+ public void setAutoShutdownEnabled(boolean enabled) {
+ if (mShutdownHook == null) {
+ if (enabled) {
+ mShutdownHook = new ShutdownHook(this);
+ try {
+ Runtime.getRuntime().addShutdownHook(mShutdownHook);
+ } catch (IllegalStateException e) {
+ // Shutdown in progress, so immediately run hook.
+ mShutdownHook.run();
+ }
+ }
+ } else {
+ if (!enabled) {
+ try {
+ Runtime.getRuntime().removeShutdownHook(mShutdownHook);
+ } catch (IllegalStateException e) {
+ // Shutdown in progress, hook is running.
+ }
+ mShutdownHook = null;
+ }
+ }
+ }
+
+ // Required by ShutdownCapability.
+ public void shutdown() {
+ shutdown(true);
+ }
+
+ private void shutdown(boolean suspendThreads) {
+ if (!mHasShutdown) {
+ // Since this repository is being closed before system shutdown,
+ // remove shutdown hook and run it now.
+ ShutdownHook hook = mShutdownHook;
+ if (hook != null) {
+ try {
+ Runtime.getRuntime().removeShutdownHook(hook);
+ } catch (IllegalStateException e) {
+ // Shutdown in progress, hook is running.
+ hook = null;
+ }
+ } else {
+ // If hook is null, auto-shutdown was disabled. Make a new
+ // instance to use, but don't register it.
+ hook = new ShutdownHook(this);
+ }
+ if (hook != null) {
+ hook.run(suspendThreads);
+ }
+ mHasShutdown = true;
+ }
+ }
+
+ // Required by SequenceCapability.
+ public SequenceValueProducer getSequenceValueProducer(String name) throws RepositoryException {
+ return mSequencePool.get(name);
+ }
+
+ /**
+ * Returns the thread-local TransactionManager, creating it if needed.
+ */
+ protected TransactionManager<Txn> localTransactionManager() {
+ TransactionManager<Txn> txnMgr = mCurrentTxnMgr.get();
+ if (txnMgr == null) {
+ lockoutShutdown();
+ try {
+ txnMgr = createTransactionManager();
+ mCurrentTxnMgr.set(txnMgr);
+ mAllTxnMgrs.put(txnMgr, null);
+ } finally {
+ unlockoutShutdown();
+ }
+ }
+ return txnMgr;
+ }
+
+ /**
+ * Call to prevent shutdown hook from running. Be sure to call
+ * unlockoutShutdown afterwards.
+ */
+ protected void lockoutShutdown() {
+ mShutdownLock.readLock().lock();
+ }
+
+ /**
+ * Only call this to release lockoutShutdown.
+ */
+ protected void unlockoutShutdown() {
+ mShutdownLock.readLock().unlock();
+ }
+
+ /**
+ * Only to be called by shutdown hook itself.
+ */
+ void lockForShutdown() {
+ mShutdownLock.writeLock().lock();
+ }
+
+ /**
+ * Only to be called by shutdown hook itself.
+ */
+ void unlockForShutdown() {
+ mShutdownLock.writeLock().unlock();
+ }
+
+ /**
+ * Returns all available Storage instances.
+ */
+ protected Collection<Storage> allStorage() {
+ return mStoragePool.values();
+ }
+
+ /**
+ * Install custom shutdown logic by overriding this method. By default it
+ * does nothing.
+ */
+ protected void shutdownHook() {
+ }
+
+ /**
+ * Return the main Log object for this Repository. If none provided, then
+ * no messages are logged by AbstractRepository.
+ */
+ protected abstract Log getLog();
+
+ /**
+ * Called upon to create a new thread-local TransactionManager instance.
+ */
+ protected abstract TransactionManager<Txn> createTransactionManager();
+
+ /**
+ * Called upon to create a new Storage instance.
+ */
+ protected abstract <S extends Storable> Storage<S> createStorage(Class<S> type)
+ throws RepositoryException;
+
+ /**
+ * Called upon to create a new SequenceValueProducer instance.
+ */
+ protected abstract SequenceValueProducer createSequenceValueProducer(String name)
+ throws RepositoryException;
+
+ void info(String message) {
+ Log log = getLog();
+ if (log != null) {
+ log.info(message);
+ }
+ }
+
+ void error(String message, Throwable e) {
+ Log log = getLog();
+ if (log != null) {
+ log.error(message, e);
+ }
+ }
+
+ private static class ShutdownHook extends Thread {
+ private final WeakReference<AbstractRepository<?>> mRepository;
+
+ ShutdownHook(AbstractRepository repository) {
+ super(repository.getClass().getSimpleName() + " shutdown (" +
+ repository.getName() + ')');
+ mRepository = new WeakReference<AbstractRepository<?>>(repository);
+ }
+
+ public void run() {
+ run(true);
+ }
+
+ public void run(boolean suspendThreads) {
+ AbstractRepository<?> repository = mRepository.get();
+ if (repository == null) {
+ return;
+ }
+
+ repository.info("Closing repository \"" + repository.getName() + '"');
+
+ try {
+ doShutdown(repository, suspendThreads);
+ } finally {
+ repository.mHasShutdown = true;
+ mRepository.clear();
+ repository.info("Finished closing repository \"" + repository.getName() + '"');
+ }
+ }
+
+ private void doShutdown(AbstractRepository<?> repository, boolean suspendThreads) {
+ repository.lockForShutdown();
+ try {
+ // Return unused sequence values.
+ repository.mSequencePool.returnReservedValues(null);
+
+ // Close transactions and cursors.
+ for (TransactionManager<?> txnMgr : repository.mAllTxnMgrs.keySet()) {
+ if (suspendThreads) {
+ // Lock transaction manager but don't release it. This
+ // prevents other threads from beginning work during
+ // shutdown, which will likely fail along the way.
+ txnMgr.getLock().lock();
+ }
+ try {
+ txnMgr.close();
+ } catch (Throwable e) {
+ repository.error("Failed to close TransactionManager", e);
+ }
+ }
+
+ repository.shutdownHook();
+ } finally {
+ repository.unlockForShutdown();
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/spi/AbstractSequenceValueProducer.java b/src/main/java/com/amazon/carbonado/spi/AbstractSequenceValueProducer.java
deleted file mode 100644
index 5badf0f..0000000
--- a/src/main/java/com/amazon/carbonado/spi/AbstractSequenceValueProducer.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * 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.math.BigInteger;
-
-import com.amazon.carbonado.PersistException;
-
-/**
- *
- *
- * @author Brian S O'Neill
- */
-public abstract class AbstractSequenceValueProducer implements SequenceValueProducer {
- protected AbstractSequenceValueProducer() {
- }
-
- public int nextIntValue() throws PersistException {
- return (int) nextLongValue();
- }
-
- public String nextDecimalValue() throws PersistException {
- return nextNumericalValue(10, 0);
- }
-
- public String nextNumericalValue(int radix, int minLength) throws PersistException {
- long next = nextLongValue();
- String str;
-
- if (next >= 0) {
- str = Long.toString(next, radix);
- } else {
- // Use BigInteger to print negative values as positive by expanding
- // precision to 72 bits
-
- byte[] bytes = new byte[9];
- bytes[8] = (byte) (next & 0xff);
- bytes[7] = (byte) ((next >>= 8) & 0xff);
- bytes[6] = (byte) ((next >>= 8) & 0xff);
- bytes[5] = (byte) ((next >>= 8) & 0xff);
- bytes[4] = (byte) ((next >>= 8) & 0xff);
- bytes[3] = (byte) ((next >>= 8) & 0xff);
- bytes[2] = (byte) ((next >>= 8) & 0xff);
- bytes[1] = (byte) ((next >>= 8) & 0xff);
- //bytes[0] = 0;
-
- str = new BigInteger(bytes).toString(radix);
- }
-
- int pad = minLength - str.length();
-
- if (pad > 0) {
- StringBuilder b = new StringBuilder(minLength);
- while (--pad >= 0) {
- b.append('0');
- }
- b.append(str);
- str = b.toString();
- }
-
- return str;
- }
-}
diff --git a/src/main/java/com/amazon/carbonado/spi/BelatedStorageCreator.java b/src/main/java/com/amazon/carbonado/spi/BelatedStorageCreator.java
index ed37df0..e0ea552 100644
--- a/src/main/java/com/amazon/carbonado/spi/BelatedStorageCreator.java
+++ b/src/main/java/com/amazon/carbonado/spi/BelatedStorageCreator.java
@@ -120,6 +120,10 @@ public class BelatedStorageCreator<S extends Storable>
return mRepo;
}
+ public void truncate() {
+ throw error();
+ }
+
public boolean addTrigger(Trigger<? super S> trigger) {
throw error();
}
diff --git a/src/main/java/com/amazon/carbonado/spi/CodeBuilderUtil.java b/src/main/java/com/amazon/carbonado/spi/CodeBuilderUtil.java
index e3b24bd..0623f94 100644
--- a/src/main/java/com/amazon/carbonado/spi/CodeBuilderUtil.java
+++ b/src/main/java/com/amazon/carbonado/spi/CodeBuilderUtil.java
@@ -21,7 +21,9 @@ import java.util.HashSet;
import java.util.Set;
import java.util.Map;
import java.util.HashMap;
+
import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
import org.cojen.classfile.ClassFile;
import org.cojen.classfile.Modifiers;
@@ -31,9 +33,11 @@ import org.cojen.classfile.Label;
import org.cojen.classfile.TypeDesc;
import org.cojen.classfile.LocalVariable;
import org.cojen.classfile.MethodDesc;
+import org.cojen.classfile.Opcode;
import org.cojen.util.ClassInjector;
import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.SupportException;
import static com.amazon.carbonado.spi.CommonMethodNames.*;
@@ -133,7 +137,7 @@ public class CodeBuilderUtil {
public static void defineCopyBridges(ClassFile cf, Class leaf) {
for (Class c : gatherAllBridgeTypes(new HashSet<Class>(), leaf)) {
if (c != Object.class) {
- defineCopyBridge(cf, c);
+ defineCopyBridge(cf, leaf, c);
}
}
}
@@ -144,11 +148,17 @@ public class CodeBuilderUtil {
* return it as the correct type.
*
* @param cf file to which to add the copy bridge
+ * @param leaf leaf class
* @param returnClass type returned from generated bridge method
*/
- public static void defineCopyBridge(ClassFile cf, Class returnClass) {
+ private static void defineCopyBridge(ClassFile cf, Class leaf, Class returnClass) {
TypeDesc returnType = TypeDesc.forClass(returnClass);
+ if (isPublicMethodFinal(leaf, COPY_METHOD_NAME, returnType, null)) {
+ // Cannot override.
+ return;
+ }
+
MethodInfo mi = cf.addMethod(Modifiers.PUBLIC.toBridge(true),
COPY_METHOD_NAME, returnType, null);
CodeBuilder b = new CodeBuilder(mi);
@@ -186,6 +196,38 @@ public class CodeBuilderUtil {
}
/**
+ * Returns true if a public final method exists which matches the given
+ * specification.
+ */
+ public static boolean isPublicMethodFinal(Class clazz, String name,
+ TypeDesc retType, TypeDesc[] params)
+ {
+ if (!clazz.isInterface()) {
+ Class[] paramClasses;
+ if (params == null || params.length == 0) {
+ paramClasses = null;
+ } else {
+ paramClasses = new Class[params.length];
+ for (int i=0; i<params.length; i++) {
+ paramClasses[i] = params[i].toClass();
+ }
+ }
+ try {
+ Method existing = clazz.getMethod(name, paramClasses);
+ if (Modifier.isFinal(existing.getModifiers())) {
+ if (TypeDesc.forClass(existing.getReturnType()) == retType) {
+ // Method is already implemented and is final.
+ return true;
+ }
+ }
+ } catch (NoSuchMethodException e) {
+ }
+ }
+
+ return false;
+ }
+
+ /**
* Define a classfile appropriate for most Storables. Specifically:
* <ul>
* <li>implements Storable</li>
@@ -382,6 +424,78 @@ public class CodeBuilderUtil {
}
/**
+ * Generates code to push an initial version property value on the stack.
+ *
+ * @throws SupportException if version type is not supported
+ */
+ public static void initialVersion(CodeBuilder b, TypeDesc type, int value)
+ throws SupportException
+ {
+ adjustVersion(b, type, value, false);
+ }
+
+ /**
+ * Generates code to increment a version property value, already on the stack.
+ *
+ * @throws SupportException if version type is not supported
+ */
+ public static void incrementVersion(CodeBuilder b, TypeDesc type)
+ throws SupportException
+ {
+ adjustVersion(b, type, 0, true);
+ }
+
+ private static void adjustVersion(CodeBuilder b, TypeDesc type, int value, boolean increment)
+ throws SupportException
+ {
+ TypeDesc primitiveType = type.toPrimitiveType();
+ supportCheck: {
+ if (primitiveType != null) {
+ switch (primitiveType.getTypeCode()) {
+ case TypeDesc.INT_CODE:
+ case TypeDesc.LONG_CODE:
+ break supportCheck;
+ }
+ }
+ throw new SupportException("Unsupported version type: " + type.getFullName());
+ }
+
+ if (!increment) {
+ if (primitiveType == TypeDesc.LONG) {
+ b.loadConstant((long) value);
+ } else {
+ b.loadConstant(value);
+ }
+ } else {
+ Label setVersion = b.createLabel();
+ if (!type.isPrimitive()) {
+ b.dup();
+ Label versionNotNull = b.createLabel();
+ b.ifNullBranch(versionNotNull, false);
+ b.pop();
+ if (primitiveType == TypeDesc.LONG) {
+ b.loadConstant(1L);
+ } else {
+ b.loadConstant(1);
+ }
+ b.branch(setVersion);
+ versionNotNull.setLocation();
+ b.convert(type, primitiveType);
+ }
+ if (primitiveType == TypeDesc.LONG) {
+ b.loadConstant(1L);
+ b.math(Opcode.LADD);
+ } else {
+ b.loadConstant(1);
+ b.math(Opcode.IADD);
+ }
+ setVersion.setLocation();
+ }
+
+ b.convert(primitiveType, type);
+ }
+
+ /**
* Determines which overloaded "with" method on Query should be bound to.
*/
public static TypeDesc bindQueryParam(Class clazz) {
@@ -403,4 +517,58 @@ public class CodeBuilderUtil {
}
return TypeDesc.OBJECT;
}
+
+ /**
+ * Appends a String to a StringBuilder. A StringBuilder and String must be
+ * on the stack, and a StringBuilder is left on the stack after the call.
+ */
+ public static void callStringBuilderAppendString(CodeBuilder b) {
+ // Because of JDK1.5 bug which exposes AbstractStringBuilder class,
+ // cannot use reflection to get method signature.
+ TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class);
+ b.invokeVirtual(stringBuilder, "append", stringBuilder, new TypeDesc[] {TypeDesc.STRING});
+ }
+
+ /**
+ * Appends a char to a StringBuilder. A StringBuilder and char must be on
+ * the stack, and a StringBuilder is left on the stack after the call.
+ */
+ public static void callStringBuilderAppendChar(CodeBuilder b) {
+ // Because of JDK1.5 bug which exposes AbstractStringBuilder class,
+ // cannot use reflection to get method signature.
+ TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class);
+ b.invokeVirtual(stringBuilder, "append", stringBuilder, new TypeDesc[] {TypeDesc.CHAR});
+ }
+
+ /**
+ * Calls length on a StringBuilder on the stack, leaving an int on the stack.
+ */
+ public static void callStringBuilderLength(CodeBuilder b) {
+ // Because of JDK1.5 bug which exposes AbstractStringBuilder class,
+ // cannot use reflection to get method signature.
+ TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class);
+ b.invokeVirtual(stringBuilder, "length", TypeDesc.INT, null);
+ }
+
+ /**
+ * Calls setLength on a StringBuilder. A StringBuilder and int must be on
+ * the stack, and both are consumed after the call.
+ */
+ public static void callStringBuilderSetLength(CodeBuilder b) {
+ // Because of JDK1.5 bug which exposes AbstractStringBuilder class,
+ // cannot use reflection to get method signature.
+ TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class);
+ b.invokeVirtual(stringBuilder, "setLength", null, new TypeDesc[] {TypeDesc.INT});
+ }
+
+ /**
+ * Calls toString on a StringBuilder. A StringBuilder must be on the stack,
+ * and a String is left on the stack after the call.
+ */
+ public static void callStringBuilderToString(CodeBuilder b) {
+ // Because of JDK1.5 bug which exposes AbstractStringBuilder class,
+ // cannot use reflection to get method signature.
+ TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class);
+ b.invokeVirtual(stringBuilder, "toString", TypeDesc.STRING, null);
+ }
}
diff --git a/src/main/java/com/amazon/carbonado/spi/LobEngine.java b/src/main/java/com/amazon/carbonado/spi/LobEngine.java
index a897cac..d91ce5e 100644
--- a/src/main/java/com/amazon/carbonado/spi/LobEngine.java
+++ b/src/main/java/com/amazon/carbonado/spi/LobEngine.java
@@ -55,6 +55,9 @@ import com.amazon.carbonado.lob.BlobClob;
import com.amazon.carbonado.lob.Clob;
import com.amazon.carbonado.lob.Lob;
+import com.amazon.carbonado.sequence.SequenceValueGenerator;
+import com.amazon.carbonado.sequence.SequenceValueProducer;
+
/**
* Complete Lob support for repositories, although repository is responsible
* for binding Lob properties to this engine. Lobs are referenced by locators,
@@ -83,16 +86,32 @@ public class LobEngine {
final Repository mRepo;
final Storage<StoredLob> mLobStorage;
final Storage<StoredLob.Block> mLobBlockStorage;
+ final SequenceValueProducer mLocatorSequence;
private Map mTriggers;
/**
- * @param repo storage for Lobs
+ * @param lobRepo storage for Lobs - should not be replicated
+ * @param locatorRepo storage for producing unique values for Lob locators
+ * - should be root repository
+ */
+ public LobEngine(Repository lobRepo, Repository locatorRepo) throws RepositoryException {
+ // Cannot reliably use sequences provided by Lob repository, since
+ // LobEngine is used internally by repositories.
+ this(lobRepo, new SequenceValueGenerator(locatorRepo, StoredLob.class.getName()));
+ }
+
+ /**
+ * @param lobRepo storage for Lobs - should not be replicated
+ * @param locatorSequenceProducer source of unique values for Lob locators
*/
- public LobEngine(Repository repo) throws RepositoryException {
- mRepo = repo;
- mLobStorage = repo.storageFor(StoredLob.class);
- mLobBlockStorage = repo.storageFor(StoredLob.Block.class);
+ public LobEngine(Repository lobRepo, SequenceValueProducer locatorSequenceProducer)
+ throws RepositoryException
+ {
+ mRepo = lobRepo;
+ mLobStorage = lobRepo.storageFor(StoredLob.class);
+ mLobBlockStorage = lobRepo.storageFor(StoredLob.Block.class);
+ mLocatorSequence = locatorSequenceProducer;
}
/**
@@ -103,6 +122,7 @@ public class LobEngine {
*/
public Blob createNewBlob(int blockSize) throws PersistException {
StoredLob lob = mLobStorage.prepare();
+ lob.setLocator(mLocatorSequence.nextLongValue());
lob.setBlockSize(blockSize);
lob.setLength(0);
lob.insert();
@@ -117,6 +137,7 @@ public class LobEngine {
*/
public Clob createNewClob(int blockSize) throws PersistException {
StoredLob lob = mLobStorage.prepare();
+ lob.setLocator(mLocatorSequence.nextLongValue());
lob.setBlockSize(blockSize);
lob.setLength(0);
lob.insert();
diff --git a/src/main/java/com/amazon/carbonado/spi/MasterStorableGenerator.java b/src/main/java/com/amazon/carbonado/spi/MasterStorableGenerator.java
index ead6c3c..3de8fb5 100644
--- a/src/main/java/com/amazon/carbonado/spi/MasterStorableGenerator.java
+++ b/src/main/java/com/amazon/carbonado/spi/MasterStorableGenerator.java
@@ -47,6 +47,8 @@ import com.amazon.carbonado.info.StorableInfo;
import com.amazon.carbonado.info.StorableIntrospector;
import com.amazon.carbonado.info.StorableProperty;
+import com.amazon.carbonado.sequence.SequenceValueProducer;
+
import static com.amazon.carbonado.spi.CommonMethodNames.*;
/**
@@ -409,7 +411,8 @@ public final class MasterStorableGenerator<S extends Storable> {
ordinal++;
if (property.isJoin() || property.isPrimaryKeyMember()
- || property.isNullable())
+ || property.isNullable()
+ || property.isAutomatic() || property.isVersion())
{
continue;
}
@@ -808,63 +811,25 @@ public final class MasterStorableGenerator<S extends Storable> {
int value)
throws SupportException
{
- StorableProperty<?> versionProperty = mInfo.getVersionProperty();
-
- TypeDesc versionType = TypeDesc.forClass(versionProperty.getType());
- TypeDesc versionPrimitiveType = versionType.toPrimitiveType();
- supportCheck: {
- if (versionPrimitiveType != null) {
- switch (versionPrimitiveType.getTypeCode()) {
- case TypeDesc.INT_CODE:
- case TypeDesc.LONG_CODE:
- break supportCheck;
- }
- }
- throw new SupportException
- ("Unsupported version type: " + versionType.getFullName());
- }
-
+ // Push storable to stack in preparation for calling set method below.
if (storableVar == null) {
b.loadThis();
} else {
b.loadLocal(storableVar);
}
+ StorableProperty<?> versionProperty = mInfo.getVersionProperty();
+ TypeDesc versionType = TypeDesc.forClass(versionProperty.getType());
+
if (value >= 0) {
- if (versionPrimitiveType == TypeDesc.LONG) {
- b.loadConstant((long) value);
- } else {
- b.loadConstant(value);
- }
+ CodeBuilderUtil.initialVersion(b, versionType, value);
} else {
+ // Load current property value.
b.dup();
b.invoke(versionProperty.getReadMethod());
- Label setVersion = b.createLabel();
- if (!versionType.isPrimitive()) {
- b.dup();
- Label versionNotNull = b.createLabel();
- b.ifNullBranch(versionNotNull, false);
- b.pop();
- if (versionPrimitiveType == TypeDesc.LONG) {
- b.loadConstant(1L);
- } else {
- b.loadConstant(1);
- }
- b.branch(setVersion);
- versionNotNull.setLocation();
- b.convert(versionType, versionPrimitiveType);
- }
- if (versionPrimitiveType == TypeDesc.LONG) {
- b.loadConstant(1L);
- b.math(Opcode.LADD);
- } else {
- b.loadConstant(1);
- b.math(Opcode.IADD);
- }
- setVersion.setLocation();
+ CodeBuilderUtil.incrementVersion(b, versionType);
}
- b.convert(versionPrimitiveType, versionType);
b.invoke(versionProperty.getWriteMethod());
}
}
diff --git a/src/main/java/com/amazon/carbonado/spi/MasterSupport.java b/src/main/java/com/amazon/carbonado/spi/MasterSupport.java
index de1e521..dd48194 100644
--- a/src/main/java/com/amazon/carbonado/spi/MasterSupport.java
+++ b/src/main/java/com/amazon/carbonado/spi/MasterSupport.java
@@ -20,6 +20,7 @@ package com.amazon.carbonado.spi;
import com.amazon.carbonado.PersistException;
import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.sequence.SequenceValueProducer;
/**
* Provides runtime support for Storable classes generated by {@link MasterStorableGenerator}.
diff --git a/src/main/java/com/amazon/carbonado/spi/SequenceValueGenerator.java b/src/main/java/com/amazon/carbonado/spi/SequenceValueGenerator.java
deleted file mode 100644
index efe8ebd..0000000
--- a/src/main/java/com/amazon/carbonado/spi/SequenceValueGenerator.java
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
- * 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 com.amazon.carbonado.FetchException;
-import com.amazon.carbonado.PersistException;
-import com.amazon.carbonado.Repository;
-import com.amazon.carbonado.RepositoryException;
-import com.amazon.carbonado.Storage;
-import com.amazon.carbonado.Transaction;
-
-/**
- * General purpose implementation of a sequence value generator.
- *
- * @author Brian S O'Neill
- * @see com.amazon.carbonado.Sequence
- * @see StoredSequence
- */
-public class SequenceValueGenerator extends AbstractSequenceValueProducer {
- private static final int DEFAULT_RESERVE_AMOUNT = 100;
- private static final int DEFAULT_INITIAL_VALUE = 1;
- private static final int DEFAULT_INCREMENT = 1;
-
- private final Repository mRepository;
- private final Storage<StoredSequence> mStorage;
- private final StoredSequence mStoredSequence;
- private final int mIncrement;
- private final int mReserveAmount;
-
- private boolean mHasReservedValues;
- private long mNextValue;
-
- /**
- * Construct a new SequenceValueGenerator which might create persistent
- * sequence data if it does not exist. The initial sequence value is one,
- * and the increment is one.
- *
- * @param repo repository to persist sequence data
- * @param name name of sequence
- */
- public SequenceValueGenerator(Repository repo, String name)
- throws RepositoryException
- {
- this(repo, name, DEFAULT_INITIAL_VALUE, DEFAULT_INCREMENT);
- }
-
- /**
- * Construct a new SequenceValueGenerator which might create persistent
- * sequence data if it does not exist.
- *
- * @param repo repository to persist sequence data
- * @param name name of sequence
- * @param initialValue initial sequence value, if sequence needs to be created
- * @param increment amount to increment sequence by
- */
- public SequenceValueGenerator(Repository repo, String name, long initialValue, int increment)
- throws RepositoryException
- {
- this(repo, name, initialValue, increment, DEFAULT_RESERVE_AMOUNT);
- }
-
- /**
- * Construct a new SequenceValueGenerator which might create persistent
- * sequence data if it does not exist.
- *
- * @param repo repository to persist sequence data
- * @param name name of sequence
- * @param initialValue initial sequence value, if sequence needs to be created
- * @param increment amount to increment sequence by
- * @param reserveAmount amount of sequence values to reserve
- */
- public SequenceValueGenerator(Repository repo, String name,
- long initialValue, int increment, int reserveAmount)
- throws RepositoryException
- {
- if (repo == null || name == null || increment < 1 || reserveAmount < 1) {
- throw new IllegalArgumentException();
- }
-
- mRepository = repo;
-
- mIncrement = increment;
- mReserveAmount = reserveAmount;
-
- mStorage = repo.storageFor(StoredSequence.class);
-
- mStoredSequence = mStorage.prepare();
- mStoredSequence.setName(name);
-
- Transaction txn = repo.enterTopTransaction(null);
- txn.setForUpdate(true);
- try {
- if (!mStoredSequence.tryLoad()) {
- mStoredSequence.setInitialValue(initialValue);
- // Start as small as possible to allow signed long comparisons to work.
- mStoredSequence.setNextValue(Long.MIN_VALUE);
- if (!mStoredSequence.tryInsert()) {
- mStoredSequence.load();
- }
- }
- txn.commit();
- } finally {
- txn.exit();
- }
- }
-
- /**
- * Reset the sequence.
- *
- * @param initialValue first value produced by sequence
- */
- public void reset(int initialValue) throws FetchException, PersistException {
- synchronized (mStoredSequence) {
- Transaction txn = mRepository.enterTopTransaction(null);
- txn.setForUpdate(true);
- try {
- boolean doUpdate = mStoredSequence.tryLoad();
- mStoredSequence.setInitialValue(initialValue);
- // Start as small as possible to allow signed long comparisons to work.
- mStoredSequence.setNextValue(Long.MIN_VALUE);
- if (doUpdate) {
- mStoredSequence.update();
- } else {
- mStoredSequence.insert();
- }
- txn.commit();
- mHasReservedValues = false;
- } finally {
- txn.exit();
- }
- }
- }
-
- /**
- * Returns the next value from the sequence, which may wrap negative if all
- * positive values are exhausted. When sequence wraps back to initial
- * value, the sequence is fully exhausted, and an exception is thrown to
- * indicate this.
- *
- * <p>Note: this method throws PersistException even for fetch failures
- * since this method is called by insert operations. Insert operations can
- * only throw a PersistException.
- *
- * @throws PersistException for fetch/persist failure or if sequence is exhausted.
- */
- public long nextLongValue() throws PersistException {
- try {
- synchronized (mStoredSequence) {
- return nextUnadjustedValue() + Long.MIN_VALUE + mStoredSequence.getInitialValue();
- }
- } catch (FetchException e) {
- throw e.toPersistException();
- }
- }
-
- /**
- * Returns the next value from the sequence, which may wrap negative if all
- * positive values are exhausted. When sequence wraps back to initial
- * value, the sequence is fully exhausted, and an exception is thrown to
- * indicate this.
- *
- * <p>Note: this method throws PersistException even for fetch failures
- * since this method is called by insert operations. Insert operations can
- * only throw a PersistException.
- *
- * @throws PersistException for fetch/persist failure or if sequence is
- * exhausted for int values.
- */
- public int nextIntValue() throws PersistException {
- try {
- synchronized (mStoredSequence) {
- long initial = mStoredSequence.getInitialValue();
- if (initial >= 0x100000000L) {
- throw new PersistException
- ("Sequence initial value too large to support 32-bit ints: " +
- mStoredSequence.getName() + ", initial: " + initial);
- }
- long next = nextUnadjustedValue();
- if (next >= Long.MIN_VALUE + 0x100000000L) {
- // Everytime we throw this exception, a long sequence value
- // has been lost. This seems fairly benign.
- throw new PersistException
- ("Sequence exhausted for 32-bit ints: " + mStoredSequence.getName() +
- ", next: " + (next + Long.MIN_VALUE + initial));
- }
- return (int) (next + Long.MIN_VALUE + initial);
- }
- } catch (FetchException e) {
- throw e.toPersistException();
- }
- }
-
- /**
- * Allow any unused reserved values to be returned for re-use. If the
- * repository is shared by other processes, then reserved values might not
- * be returnable.
- *
- * <p>This method should be called during the shutdown process of a
- * repository, although calling it does not invalidate this
- * SequenceValueGenerator. If getNextValue is called again, it will reserve
- * values again.
- *
- * @return true if reserved values were returned
- */
- public boolean returnReservedValues() throws FetchException, PersistException {
- synchronized (mStoredSequence) {
- if (mHasReservedValues) {
- Transaction txn = mRepository.enterTopTransaction(null);
- txn.setForUpdate(true);
- try {
- // Compare known StoredSequence with current persistent
- // one. If same, then reserved values can be returned.
- StoredSequence current = mStorage.prepare();
- current.setName(mStoredSequence.getName());
- if (current.tryLoad() && current.equals(mStoredSequence)) {
- mStoredSequence.setNextValue(mNextValue + mIncrement);
- mStoredSequence.update();
- txn.commit();
- mHasReservedValues = false;
- return true;
- }
- } finally {
- txn.exit();
- }
- }
- }
- return false;
- }
-
- // Assumes caller has synchronized on mStoredSequence
- private long nextUnadjustedValue() throws FetchException, PersistException {
- if (mHasReservedValues) {
- long next = mNextValue + mIncrement;
- mNextValue = next;
- if (next < mStoredSequence.getNextValue()) {
- return next;
- }
- mHasReservedValues = false;
- }
-
- Transaction txn = mRepository.enterTopTransaction(null);
- txn.setForUpdate(true);
- try {
- // Assume that StoredSequence is stale, so reload.
- mStoredSequence.load();
- long next = mStoredSequence.getNextValue();
- long nextStored = next + mReserveAmount * mIncrement;
-
- if (next >= 0 && nextStored < 0) {
- // Wrapped around. There might be just a few values left.
- long avail = (Long.MAX_VALUE - next) / mIncrement;
- if (avail > 0) {
- nextStored = next + avail * mIncrement;
- } else {
- // Throw a PersistException since sequences are applied during
- // insert operations, and inserts can only throw PersistExceptions.
- throw new PersistException
- ("Sequence exhausted: " + mStoredSequence.getName());
- }
- }
-
- mStoredSequence.setNextValue(nextStored);
- mStoredSequence.update();
-
- txn.commit();
-
- mNextValue = next;
- mHasReservedValues = true;
- return next;
- } finally {
- txn.exit();
- }
- }
-}
diff --git a/src/main/java/com/amazon/carbonado/spi/SequenceValueProducer.java b/src/main/java/com/amazon/carbonado/spi/SequenceValueProducer.java
deleted file mode 100644
index 659de89..0000000
--- a/src/main/java/com/amazon/carbonado/spi/SequenceValueProducer.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * 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 com.amazon.carbonado.PersistException;
-
-/**
- * Produces values for sequences.
- *
- * @author Brian S O'Neill
- * @see com.amazon.carbonado.Sequence
- */
-public interface SequenceValueProducer {
- /**
- * Returns the next value from the sequence, which may wrap negative if all
- * positive values are exhausted. When sequence wraps back to initial
- * value, the sequence is fully exhausted, and an exception is thrown to
- * indicate this.
- *
- * <p>Note: this method throws PersistException even for fetch failures
- * since this method is called by insert operations. Insert operations can
- * only throw a PersistException.
- *
- * @throws PersistException for fetch/persist failure or if sequence is exhausted.
- */
- public long nextLongValue() throws PersistException;
-
- /**
- * Returns the next value from the sequence, which may wrap negative if all
- * positive values are exhausted. When sequence wraps back to initial
- * value, the sequence is fully exhausted, and an exception is thrown to
- * indicate this.
- *
- * <p>Note: this method throws PersistException even for fetch failures
- * since this method is called by insert operations. Insert operations can
- * only throw a PersistException.
- *
- * @throws PersistException for fetch/persist failure or if sequence is
- * exhausted for int values.
- */
- public int nextIntValue() throws PersistException;
-
- /**
- * Returns the next decimal string value from the sequence, which remains
- * positive. When sequence wraps back to initial value, the sequence is
- * fully exhausted, and an exception is thrown to indicate this.
- *
- * <p>Note: this method throws PersistException even for fetch failures
- * since this method is called by insert operations. Insert operations can
- * only throw a PersistException.
- *
- * @throws PersistException for fetch/persist failure or if sequence is exhausted.
- */
- public String nextDecimalValue() throws PersistException;
-
- /**
- * Returns the next numerical string value from the sequence, which remains
- * positive. When sequence wraps back to initial value, the sequence is
- * fully exhausted, and an exception is thrown to indicate this.
- *
- * <p>Note: this method throws PersistException even for fetch failures
- * since this method is called by insert operations. Insert operations can
- * only throw a PersistException.
- *
- * @param radix use 2 for binary, 10 for decimal, 16 for hex. Max is 36.
- * @param minLength ensure string is at least this long (padded with zeros if
- * necessary) to ensure proper string sort
- * @throws PersistException for fetch/persist failure or if sequence is exhausted.
- */
- public String nextNumericalValue(int radix, int minLength) throws PersistException;
-}
diff --git a/src/main/java/com/amazon/carbonado/spi/StorableGenerator.java b/src/main/java/com/amazon/carbonado/spi/StorableGenerator.java
index 93f9b38..0c2d50d 100644
--- a/src/main/java/com/amazon/carbonado/spi/StorableGenerator.java
+++ b/src/main/java/com/amazon/carbonado/spi/StorableGenerator.java
@@ -24,6 +24,7 @@ import java.lang.ref.SoftReference;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.math.BigInteger;
@@ -583,8 +584,11 @@ public final class StorableGenerator<S extends Storable> {
requireStateField = true;
} else if (mGenMode == GEN_ABSTRACT) {
// Only define regular property fields if abstract
- // class. Wrapped class doesn't reference them.
- mClassFile.addField(Modifiers.PROTECTED, name, type);
+ // class. Wrapped class doesn't reference them. Double
+ // words are volatile to prevent word tearing without
+ // explicit synchronization.
+ mClassFile.addField(Modifiers.PROTECTED.toVolatile(type.isDoubleWord()),
+ name, type);
requireStateField = true;
}
@@ -617,11 +621,9 @@ public final class StorableGenerator<S extends Storable> {
}
}
- if (mGenMode == GEN_ABSTRACT && (type.isDoubleWord() || property.isJoin())) {
- // Even if read method just reads a field,
- // synchronization is needed if type is a double
- // word. Synchronization is also required for join
- // property accessors, as they may alter bit masks.
+ if (mGenMode == GEN_ABSTRACT && property.isJoin()) {
+ // Synchronization is required for join property
+ // accessors, as they may alter bit masks.
mi.setModifiers(mi.getModifiers().toSynchronized(true));
}
@@ -1061,12 +1063,6 @@ public final class StorableGenerator<S extends Storable> {
(Modifiers.PROTECTED, readName, toType, null);
mi.markSynthetic();
- if (type.isDoubleWord()) {
- // Even if read method just reads a field,
- // synchronization is needed if type is a double word.
- mi.setModifiers(mi.getModifiers().toSynchronized(true));
- }
-
// Now add code that actually gets the property value and
// then invokes adapt method.
CodeBuilder b = new CodeBuilder(mi);
@@ -1130,9 +1126,14 @@ public final class StorableGenerator<S extends Storable> {
// Add tryLoad method which delegates to abstract doTryLoad method.
addTryLoad: {
// Define the tryLoad method.
- MethodInfo mi = mClassFile.addMethod
+ MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT),
TRY_LOAD_METHOD_NAME, TypeDesc.BOOLEAN, null);
+
+ if (mi == null) {
+ break addTryLoad;
+ }
+
mi.addException(TypeDesc.forClass(FetchException.class));
if (mGenMode == GEN_WRAPPED) {
@@ -1269,9 +1270,14 @@ public final class StorableGenerator<S extends Storable> {
// Add load method which calls tryLoad.
addLoad: {
// Define the load method.
- MethodInfo mi = mClassFile.addMethod
+ MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT),
LOAD_METHOD_NAME, null, null);
+
+ if (mi == null) {
+ break addLoad;
+ }
+
mi.addException(TypeDesc.forClass(FetchException.class));
if (mGenMode == GEN_WRAPPED) {
@@ -1370,8 +1376,13 @@ public final class StorableGenerator<S extends Storable> {
// Add insert method which calls insert(forTry = false)
addInsert: {
- MethodInfo mi = mClassFile.addMethod
+ MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC, INSERT_METHOD_NAME, null, null);
+
+ if (mi == null) {
+ break addInsert;
+ }
+
mi.addException(TypeDesc.forClass(PersistException.class));
if (mGenMode == GEN_WRAPPED) {
@@ -1391,8 +1402,13 @@ public final class StorableGenerator<S extends Storable> {
// Add tryInsert method which calls insert(forTry = true)
addTryInsert: {
- MethodInfo mi = mClassFile.addMethod
+ MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC, TRY_INSERT_METHOD_NAME, TypeDesc.BOOLEAN, null);
+
+ if (mi == null) {
+ break addTryInsert;
+ }
+
mi.addException(TypeDesc.forClass(PersistException.class));
if (mGenMode == GEN_WRAPPED) {
@@ -1534,8 +1550,13 @@ public final class StorableGenerator<S extends Storable> {
// Add update method which calls update(forTry = false)
addUpdate: {
- MethodInfo mi = mClassFile.addMethod
+ MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC, UPDATE_METHOD_NAME, null, null);
+
+ if (mi == null) {
+ break addUpdate;
+ }
+
mi.addException(TypeDesc.forClass(PersistException.class));
if (mGenMode == GEN_WRAPPED) {
@@ -1555,8 +1576,13 @@ public final class StorableGenerator<S extends Storable> {
// Add tryUpdate method which calls update(forTry = true)
addTryUpdate: {
- MethodInfo mi = mClassFile.addMethod
+ MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC, TRY_UPDATE_METHOD_NAME, TypeDesc.BOOLEAN, null);
+
+ if (mi == null) {
+ break addTryUpdate;
+ }
+
mi.addException(TypeDesc.forClass(PersistException.class));
if (mGenMode == GEN_WRAPPED) {
@@ -1642,8 +1668,13 @@ public final class StorableGenerator<S extends Storable> {
// Add delete method which calls delete(forTry = false)
addDelete: {
// Define the delete method.
- MethodInfo mi = mClassFile.addMethod
+ MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC, DELETE_METHOD_NAME, null, null);
+
+ if (mi == null) {
+ break addDelete;
+ }
+
mi.addException(TypeDesc.forClass(PersistException.class));
if (mGenMode == GEN_WRAPPED) {
@@ -1664,8 +1695,13 @@ public final class StorableGenerator<S extends Storable> {
// Add tryDelete method which calls delete(forTry = true)
addTryDelete: {
// Define the delete method.
- MethodInfo mi = mClassFile.addMethod
+ MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC, TRY_DELETE_METHOD_NAME, TypeDesc.BOOLEAN, null);
+
+ if (mi == null) {
+ break addTryDelete;
+ }
+
mi.addException(TypeDesc.forClass(PersistException.class));
if (mGenMode == GEN_WRAPPED) {
@@ -1683,24 +1719,34 @@ public final class StorableGenerator<S extends Storable> {
}
// Add storableType method
- {
+ addStorableType: {
final TypeDesc type = TypeDesc.forClass(mStorableType);
final TypeDesc storableClassType = TypeDesc.forClass(Class.class);
- MethodInfo mi = mClassFile.addMethod
+ MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC, STORABLE_TYPE_METHOD_NAME, storableClassType, null);
+
+ if (mi == null) {
+ break addStorableType;
+ }
+
CodeBuilder b = new CodeBuilder(mi);
b.loadConstant(type);
b.returnValue(storableClassType);
}
- // Add copy methods.
- {
+ // Add copy method.
+ addCopy: {
TypeDesc type = TypeDesc.forClass(mInfo.getStorableType());
// Add copy method.
- MethodInfo mi = mClassFile.addMethod
+ MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT),
COPY_METHOD_NAME, mClassFile.getType(), null);
+
+ if (mi == null) {
+ break addCopy;
+ }
+
CodeBuilder b = new CodeBuilder(mi);
b.loadThis();
b.invokeVirtual(CLONE_METHOD_NAME, TypeDesc.OBJECT, null);
@@ -1739,10 +1785,12 @@ public final class StorableGenerator<S extends Storable> {
}
b.returnValue(type);
-
- CodeBuilderUtil.defineCopyBridges(mClassFile, mInfo.getStorableType());
}
+ // Part of properly defining copy method, except needs to be added even
+ // if copy method was not added because it is inherited and final.
+ CodeBuilderUtil.defineCopyBridges(mClassFile, mInfo.getStorableType());
+
// Create all the property copier methods.
// Boolean params: pkProperties, versionProperty, dataProperties, unequalOnly, dirtyOnly
addCopyPropertiesMethod(COPY_ALL_PROPERTIES,
@@ -1758,9 +1806,13 @@ public final class StorableGenerator<S extends Storable> {
// Define hasDirtyProperties method.
addHasDirtyProps: {
- MethodInfo mi = mClassFile.addMethod
+ MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC, HAS_DIRTY_PROPERTIES, TypeDesc.BOOLEAN, null);
+ if (mi == null) {
+ break addHasDirtyProps;
+ }
+
if (mGenMode == GEN_WRAPPED) {
callWrappedStorable(mi);
break addHasDirtyProps;
@@ -1783,10 +1835,15 @@ public final class StorableGenerator<S extends Storable> {
addPropertyStateCheckMethod(IS_PROPERTY_CLEAN, PROPERTY_STATE_CLEAN);
// Define isPropertySupported method.
- {
- MethodInfo mi = mClassFile.addMethod
+ addIsPropertySupported: {
+ MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC, IS_PROPERTY_SUPPORTED,
TypeDesc.BOOLEAN, new TypeDesc[] {TypeDesc.STRING});
+
+ if (mi == null) {
+ break addIsPropertySupported;
+ }
+
CodeBuilder b = new CodeBuilder(mi);
b.loadThis();
@@ -2027,12 +2084,16 @@ public final class StorableGenerator<S extends Storable> {
TypeDesc[] param = { TypeDesc.forClass(Storable.class) };
TypeDesc storableTypeDesc = TypeDesc.forClass(mStorableType);
- MethodInfo mi= mClassFile.addMethod
+ MethodInfo mi= addMethodIfNotFinal
(Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT),
methodName,
null,
param);
+ if (mi == null) {
+ return;
+ }
+
if (mGenMode == GEN_WRAPPED) {
callWrappedStorable(mi);
return;
@@ -2298,7 +2359,13 @@ public final class StorableGenerator<S extends Storable> {
}
private void addMarkCleanMethod(String name) {
- MethodInfo mi = mClassFile.addMethod(Modifiers.PUBLIC, name, null, null);
+ MethodInfo mi =
+ addMethodIfNotFinal (Modifiers.PUBLIC.toSynchronized(true), name, null, null);
+
+ if (mi == null) {
+ return;
+ }
+
CodeBuilder b = new CodeBuilder(mi);
if (mGenMode == GEN_WRAPPED) {
@@ -2353,7 +2420,13 @@ public final class StorableGenerator<S extends Storable> {
}
private void addMarkDirtyMethod(String name) {
- MethodInfo mi = mClassFile.addMethod(Modifiers.PUBLIC, name, null, null);
+ MethodInfo mi =
+ addMethodIfNotFinal(Modifiers.PUBLIC.toSynchronized(true), name, null, null);
+
+ if (mi == null) {
+ return;
+ }
+
CodeBuilder b = new CodeBuilder(mi);
if (mGenMode == GEN_WRAPPED) {
@@ -2506,6 +2579,22 @@ public final class StorableGenerator<S extends Storable> {
private void addIsInitializedMethod
(String name, Map<String, ? extends StorableProperty<S>> properties)
{
+ // Don't check Automatic properties.
+ {
+ boolean cloned = false;
+ for (StorableProperty<S> prop : properties.values()) {
+ if (prop.isAutomatic() || prop.isVersion()) {
+ if (!cloned) {
+ properties = new LinkedHashMap<String, StorableProperty<S>>(properties);
+ cloned = true;
+ }
+ // This isn't concurrent modification since the loop is
+ // still operating on the original properties map.
+ properties.remove(prop.getName());
+ }
+ }
+ }
+
MethodInfo mi = mClassFile.addMethod(Modifiers.PROTECTED, name, TypeDesc.BOOLEAN, null);
CodeBuilder b = new CodeBuilder(mi);
@@ -2624,7 +2713,7 @@ public final class StorableGenerator<S extends Storable> {
CodeBuilder b = new CodeBuilder(mi);
// Generate big switch statement that operates on Strings. See also
- // cojen.util.BeanPropertyAccessor, which also generates this kind of
+ // org.cojen.util.BeanPropertyAccessor, which also generates this kind of
// switch.
// For switch case count, obtain a prime number, at least twice as
@@ -2792,8 +2881,13 @@ public final class StorableGenerator<S extends Storable> {
* @param state property state to check
*/
private void addPropertyStateCheckMethod(String name, int state) {
- MethodInfo mi = mClassFile.addMethod(Modifiers.PUBLIC, name,
- TypeDesc.BOOLEAN, new TypeDesc[] {TypeDesc.STRING});
+ MethodInfo mi = addMethodIfNotFinal(Modifiers.PUBLIC, name,
+ TypeDesc.BOOLEAN, new TypeDesc[] {TypeDesc.STRING});
+
+ if (mi == null) {
+ return;
+ }
+
CodeBuilder b = new CodeBuilder(mi);
if (mGenMode == GEN_WRAPPED) {
@@ -2825,7 +2919,11 @@ public final class StorableGenerator<S extends Storable> {
*/
private void addHashCodeMethod() {
Modifiers modifiers = Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT);
- MethodInfo mi = mClassFile.addMethod(modifiers, "hashCode", TypeDesc.INT, null);
+ MethodInfo mi = addMethodIfNotFinal(modifiers, "hashCode", TypeDesc.INT, null);
+
+ if (mi == null) {
+ return;
+ }
if (mGenMode == GEN_WRAPPED) {
callWrappedStorable(mi);
@@ -2968,9 +3066,13 @@ public final class StorableGenerator<S extends Storable> {
}
Modifiers modifiers = Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT);
- MethodInfo mi = mClassFile.addMethod
+ MethodInfo mi = addMethodIfNotFinal
(modifiers, equalsMethodName, TypeDesc.BOOLEAN, objectParam);
+ if (mi == null) {
+ return;
+ }
+
if (mGenMode == GEN_WRAPPED && equalityType != EQUAL_FULL) {
callWrappedStorable(mi);
return;
@@ -3051,11 +3153,15 @@ public final class StorableGenerator<S extends Storable> {
TypeDesc[] charParam = {TypeDesc.CHAR};
Modifiers modifiers = Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT);
- MethodInfo mi = mClassFile.addMethod(modifiers,
- keyOnly ?
- TO_STRING_KEY_ONLY_METHOD_NAME :
- TO_STRING_METHOD_NAME,
- TypeDesc.STRING, null);
+ MethodInfo mi = addMethodIfNotFinal(modifiers,
+ keyOnly ?
+ TO_STRING_KEY_ONLY_METHOD_NAME :
+ TO_STRING_METHOD_NAME,
+ TypeDesc.STRING, null);
+
+ if (mi == null) {
+ return;
+ }
if (mGenMode == GEN_WRAPPED) {
callWrappedStorable(mi);
@@ -3548,4 +3654,18 @@ public final class StorableGenerator<S extends Storable> {
new TypeDesc[] {threadType, TypeDesc.forClass(Throwable.class)});
b.returnVoid();
}
+
+ /**
+ * @return MethodInfo for completing definition or null if superclass
+ * already implements method as final.
+ */
+ private MethodInfo addMethodIfNotFinal(Modifiers modifiers, String name,
+ TypeDesc retType, TypeDesc[] params)
+ {
+ if (CodeBuilderUtil.isPublicMethodFinal(mStorableType, name, retType, params)) {
+ return null;
+ }
+
+ return mClassFile.addMethod(modifiers, name, retType, params);
+ }
}
diff --git a/src/main/java/com/amazon/carbonado/spi/StorableIndexSet.java b/src/main/java/com/amazon/carbonado/spi/StorableIndexSet.java
index 965880e..2f943ea 100644
--- a/src/main/java/com/amazon/carbonado/spi/StorableIndexSet.java
+++ b/src/main/java/com/amazon/carbonado/spi/StorableIndexSet.java
@@ -201,6 +201,26 @@ public class StorableIndexSet<S extends Storable> extends TreeSet<StorableIndex<
}
/**
+ * Marks all indexes as clustered or non-clustered.
+ *
+ * @param clustered true to mark clustered; false to mark non-clustered
+ * @see StorableIndex#isClustered()
+ */
+ public void markClustered(boolean clustered) {
+ Map<StorableIndex<S>, StorableIndex<S>> replacements = null;
+ for (StorableIndex<S> index : this) {
+ StorableIndex<S> replacement = index.clustered(clustered);
+ if (replacement != index) {
+ if (replacements == null) {
+ replacements = new HashMap<StorableIndex<S>, StorableIndex<S>>();
+ }
+ replacements.put(index, replacement);
+ }
+ }
+ replaceEntries(replacements);
+ }
+
+ /**
* Augment non-unique indexes with primary key properties, thus making them
* unique.
*
diff --git a/src/main/java/com/amazon/carbonado/spi/StorageCollection.java b/src/main/java/com/amazon/carbonado/spi/StorageCollection.java
deleted file mode 100644
index c75f350..0000000
--- a/src/main/java/com/amazon/carbonado/spi/StorageCollection.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * 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.IdentityHashMap;
-
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.ConcurrentHashMap;
-
-import com.amazon.carbonado.MalformedTypeException;
-import com.amazon.carbonado.RepositoryException;
-import com.amazon.carbonado.Storable;
-import com.amazon.carbonado.Storage;
-import com.amazon.carbonado.SupportException;
-
-import com.amazon.carbonado.info.StorableIntrospector;
-
-/**
- * Thread-safe container of Storage instances which creates Storage
- * on-demand. If multiple threads are requesting the same Storage concurrently,
- * only one thread actually creates the Storage. The other waits for it to
- * become available.
- *
- * @author Brian S O'Neill
- */
-public abstract class StorageCollection {
- private final ConcurrentMap<Class<?>, Storage> mStorageMap;
- private final ConcurrentMap<Class<?>, Object> mStorableTypeLockMap;
-
- public StorageCollection() {
- mStorageMap = new ConcurrentHashMap<Class<?>, Storage>();
- mStorableTypeLockMap = new ConcurrentHashMap<Class<?>, Object>();
- }
-
- public <S extends Storable> Storage<S> storageFor(Class<S> type)
- throws MalformedTypeException, SupportException, RepositoryException
- {
- Storage storage = mStorageMap.get(type);
- if (storage != null) {
- return storage;
- }
-
- getLock: while (true) {
- Object lock;
- boolean doCreate;
-
- {
- Object newLock = new Object();
- Object existingLock = mStorableTypeLockMap.putIfAbsent(type, newLock);
-
- if (existingLock == null) {
- lock = newLock;
- doCreate = true;
- } else {
- lock = existingLock;
- doCreate = false;
- }
- }
-
- if (Thread.holdsLock(lock)) {
- throw new IllegalStateException
- ("Recursively trying to create storage for type: " + type);
- }
-
- synchronized (lock) {
- try {
- // Check storage map again before creating new storage.
- checkLock: while (true) {
- storage = mStorageMap.get(type);
- if (storage != null) {
- return storage;
- }
- if (mStorableTypeLockMap.get(type) != lock) {
- // Lock has changed, so get a new lock.
- continue getLock;
- }
- if (doCreate) {
- break checkLock;
- }
- try {
- // Wait with a timeout to handle rare race condition.
- lock.wait(100);
- } catch (InterruptedException e) {
- throw new RepositoryException("Interrupted");
- }
- }
-
- // Examine and throw exception early if there is a problem.
- StorableIntrospector.examine(type);
-
- storage = createStorage(type);
-
- mStorageMap.put(type, storage);
- break getLock;
- } finally {
- // Storable type lock no longer needed.
- mStorableTypeLockMap.remove(type, lock);
- lock.notifyAll();
- }
- }
- }
-
- return storage;
- }
-
- public Iterable<Storage> allStorage() {
- return mStorageMap.values();
- }
-
- protected abstract <S extends Storable> Storage<S> createStorage(Class<S> type)
- throws SupportException, RepositoryException;
-}
diff --git a/src/main/java/com/amazon/carbonado/spi/StoragePool.java b/src/main/java/com/amazon/carbonado/spi/StoragePool.java
new file mode 100644
index 0000000..9382723
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/spi/StoragePool.java
@@ -0,0 +1,59 @@
+/*
+ * 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 com.amazon.carbonado.MalformedTypeException;
+import com.amazon.carbonado.RepositoryException;
+import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.Storage;
+import com.amazon.carbonado.SupportException;
+
+import com.amazon.carbonado.util.AbstractPool;
+
+/**
+ * A concurrent pool of strongly referenced Storage instances mapped by
+ * Storable type. Storage instances are lazily created and pooled.
+ *
+ * @author Brian S O'Neill
+ */
+public abstract class StoragePool
+ extends AbstractPool<Class, Storage, RepositoryException>
+{
+ public StoragePool() {
+ }
+
+ /**
+ * Returns a Storage instance for the given Storable type, which is lazily
+ * created and pooled. If multiple threads are requesting upon the same type
+ * concurrently, at most one thread attempts to lazily create the
+ * Storage. The others wait for it to become available.
+ */
+ public <S extends Storable> Storage<S> getStorage(Class<S> type)
+ throws MalformedTypeException, SupportException, RepositoryException
+ {
+ return (Storage<S>) super.get(type);
+ }
+
+ protected final Storage create(Class type) throws SupportException, RepositoryException {
+ return createStorage(type);
+ }
+
+ protected abstract <S extends Storable> Storage<S> createStorage(Class<S> type)
+ throws SupportException, RepositoryException;
+}
diff --git a/src/main/java/com/amazon/carbonado/spi/StoredLob.java b/src/main/java/com/amazon/carbonado/spi/StoredLob.java
index 2435cb1..e409e60 100644
--- a/src/main/java/com/amazon/carbonado/spi/StoredLob.java
+++ b/src/main/java/com/amazon/carbonado/spi/StoredLob.java
@@ -19,10 +19,8 @@
package com.amazon.carbonado.spi;
import com.amazon.carbonado.Alias;
-import com.amazon.carbonado.FetchException;
import com.amazon.carbonado.Independent;
import com.amazon.carbonado.PrimaryKey;
-import com.amazon.carbonado.Sequence;
import com.amazon.carbonado.Storable;
import com.amazon.carbonado.Version;
@@ -38,7 +36,6 @@ import com.amazon.carbonado.constraint.IntegerConstraint;
@Independent
@Alias("CARBONADO_LOB")
public abstract class StoredLob implements Storable<StoredLob> {
- @Sequence("com.amazon.carbonado.spi.StoredLob")
public abstract long getLocator();
public abstract void setLocator(long locator);
diff --git a/src/main/java/com/amazon/carbonado/spi/WrappedStorage.java b/src/main/java/com/amazon/carbonado/spi/WrappedStorage.java
index 17db288..71f6fbc 100644
--- a/src/main/java/com/amazon/carbonado/spi/WrappedStorage.java
+++ b/src/main/java/com/amazon/carbonado/spi/WrappedStorage.java
@@ -78,6 +78,10 @@ public abstract class WrappedStorage<S extends Storable> implements Storage<S> {
return wrap(mStorage.query(filter));
}
+ public void truncate() throws PersistException {
+ mStorage.truncate();
+ }
+
public boolean addTrigger(Trigger<? super S> trigger) {
return mTriggerManager.addTrigger(trigger);
}