From 8809341248c62b15b78d7e6d8e06ab2ec3793c8e Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" Date: Wed, 28 Mar 2007 22:00:24 +0000 Subject: Merged 1.2-dev to trunk. --- src/main/java/com/amazon/carbonado/Automatic.java | 53 ++ .../amazon/carbonado/CorruptEncodingException.java | 4 + src/main/java/com/amazon/carbonado/Storage.java | 10 + .../amazon/carbonado/cursor/FilteredCursor.java | 24 + .../com/amazon/carbonado/info/ChainedProperty.java | 25 +- .../carbonado/info/StorableIntrospector.java | 29 +- .../amazon/carbonado/info/StorableProperty.java | 9 + .../com/amazon/carbonado/layout/StoredLayout.java | 1 - .../carbonado/layout/StoredLayoutProperty.java | 1 - .../amazon/carbonado/raw/CustomStorableCodec.java | 39 ++ .../carbonado/raw/CustomStorableCodecFactory.java | 40 ++ .../carbonado/raw/GenericEncodingStrategy.java | 5 +- .../carbonado/raw/GenericInstanceFactory.java | 6 +- .../amazon/carbonado/raw/GenericStorableCodec.java | 120 +++-- .../carbonado/raw/GenericStorableCodecFactory.java | 23 +- .../com/amazon/carbonado/raw/StorableCodec.java | 31 +- .../amazon/carbonado/raw/StorableCodecFactory.java | 16 + .../carbonado/repo/indexed/IndexEntryAccessor.java | 1 - .../repo/indexed/IndexEntryGenerator.java | 1 - .../carbonado/repo/indexed/IndexedRepository.java | 17 +- .../repo/indexed/IndexedRepositoryBuilder.java | 21 +- .../carbonado/repo/indexed/IndexedStorage.java | 92 +++- .../com/amazon/carbonado/repo/jdbc/JDBCBlob.java | 4 +- .../com/amazon/carbonado/repo/jdbc/JDBCClob.java | 4 +- .../com/amazon/carbonado/repo/jdbc/JDBCCursor.java | 53 +- .../amazon/carbonado/repo/jdbc/JDBCRepository.java | 325 ++++++------ .../carbonado/repo/jdbc/JDBCRepositoryBuilder.java | 89 +++- .../repo/jdbc/JDBCSequenceValueProducer.java | 7 +- .../carbonado/repo/jdbc/JDBCStorableGenerator.java | 560 +++++++++++++-------- .../carbonado/repo/jdbc/JDBCStorableInfo.java | 6 + .../repo/jdbc/JDBCStorableIntrospector.java | 67 ++- .../carbonado/repo/jdbc/JDBCStorableProperty.java | 6 + .../amazon/carbonado/repo/jdbc/JDBCStorage.java | 91 +++- .../carbonado/repo/jdbc/JDBCSupportStrategy.java | 107 ++-- .../repo/jdbc/LoggingCallableStatement.java | 2 - .../carbonado/repo/jdbc/LoggingConnection.java | 2 - .../carbonado/repo/jdbc/LoggingDataSource.java | 10 +- .../repo/jdbc/LoggingPreparedStatement.java | 2 - .../carbonado/repo/jdbc/LoggingStatement.java | 2 - .../carbonado/repo/jdbc/MysqlSupportStrategy.java | 4 + .../carbonado/repo/jdbc/OracleSupportStrategy.java | 25 +- .../carbonado/repo/jdbc/SimpleDataSource.java | 5 +- .../carbonado/repo/logging/LoggingRepository.java | 8 +- .../carbonado/repo/logging/LoggingStorage.java | 8 + .../repo/replicated/ReplicatedRepository.java | 8 +- .../repo/replicated/ReplicatedStorage.java | 6 + .../amazon/carbonado/repo/sleepycat/BDBCursor.java | 6 +- .../carbonado/repo/sleepycat/BDBRepository.java | 451 ++++------------- .../repo/sleepycat/BDBRepositoryBuilder.java | 25 +- .../carbonado/repo/sleepycat/BDBStorage.java | 100 +++- .../sequence/AbstractSequenceValueProducer.java | 79 +++ .../carbonado/sequence/SequenceCapability.java | 38 ++ .../carbonado/sequence/SequenceValueGenerator.java | 321 ++++++++++++ .../carbonado/sequence/SequenceValueProducer.java | 103 ++++ .../sequence/SequenceValueProducerPool.java | 77 +++ .../amazon/carbonado/sequence/StoredSequence.java | 70 +++ .../amazon/carbonado/sequence/package-info.java | 26 + .../amazon/carbonado/spi/AbstractRepository.java | 368 ++++++++++++++ .../spi/AbstractSequenceValueProducer.java | 79 --- .../carbonado/spi/BelatedStorageCreator.java | 4 + .../com/amazon/carbonado/spi/CodeBuilderUtil.java | 172 ++++++- .../java/com/amazon/carbonado/spi/LobEngine.java | 31 +- .../carbonado/spi/MasterStorableGenerator.java | 57 +-- .../com/amazon/carbonado/spi/MasterSupport.java | 1 + .../carbonado/spi/SequenceValueGenerator.java | 290 ----------- .../carbonado/spi/SequenceValueProducer.java | 87 ---- .../amazon/carbonado/spi/StorableGenerator.java | 208 ++++++-- .../com/amazon/carbonado/spi/StorableIndexSet.java | 20 + .../amazon/carbonado/spi/StorageCollection.java | 128 ----- .../java/com/amazon/carbonado/spi/StoragePool.java | 59 +++ .../java/com/amazon/carbonado/spi/StoredLob.java | 3 - .../com/amazon/carbonado/spi/WrappedStorage.java | 4 + .../SyntheticStorableReferenceBuilder.java | 2 - .../com/amazon/carbonado/util/AbstractPool.java | 94 ++++ .../amazon/carbonado/util/AbstractWeakPool.java | 143 ++++++ .../carbonado/util/WeakReentrantLockPool.java | 45 ++ .../util/WeakReentrantReadWriteLockPool.java | 45 ++ 77 files changed, 3428 insertions(+), 1677 deletions(-) create mode 100644 src/main/java/com/amazon/carbonado/Automatic.java create mode 100644 src/main/java/com/amazon/carbonado/sequence/AbstractSequenceValueProducer.java create mode 100644 src/main/java/com/amazon/carbonado/sequence/SequenceCapability.java create mode 100644 src/main/java/com/amazon/carbonado/sequence/SequenceValueGenerator.java create mode 100644 src/main/java/com/amazon/carbonado/sequence/SequenceValueProducer.java create mode 100644 src/main/java/com/amazon/carbonado/sequence/SequenceValueProducerPool.java create mode 100644 src/main/java/com/amazon/carbonado/sequence/StoredSequence.java create mode 100644 src/main/java/com/amazon/carbonado/sequence/package-info.java create mode 100644 src/main/java/com/amazon/carbonado/spi/AbstractRepository.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/AbstractSequenceValueProducer.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/SequenceValueGenerator.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/SequenceValueProducer.java delete mode 100644 src/main/java/com/amazon/carbonado/spi/StorageCollection.java create mode 100644 src/main/java/com/amazon/carbonado/spi/StoragePool.java create mode 100644 src/main/java/com/amazon/carbonado/util/AbstractPool.java create mode 100644 src/main/java/com/amazon/carbonado/util/AbstractWeakPool.java create mode 100644 src/main/java/com/amazon/carbonado/util/WeakReentrantLockPool.java create mode 100644 src/main/java/com/amazon/carbonado/util/WeakReentrantReadWriteLockPool.java (limited to 'src/main/java') diff --git a/src/main/java/com/amazon/carbonado/Automatic.java b/src/main/java/com/amazon/carbonado/Automatic.java new file mode 100644 index 0000000..96200e1 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/Automatic.java @@ -0,0 +1,53 @@ +/* + * 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; + +import java.lang.annotation.*; + +/** + * Identifies a {@link Storable} property capable of selecting its own value on + * insert. The actual process by which a value is automatically assigned is + * repository dependent. In the JDBC repository, the value might come from an + * auto-increment column or a database-specific trigger. + * + *

If the underlying repository doesn't automatically supply a value to an + * automatic property, no immediate warning is given and instead the property + * will be assigned a default value of null or zero. This may cause problems if + * the automatic property is a member of a key. Explicitly specifying a value + * can sometimes be used to bypass the automatic value altogether. + * + *

Example:

+ * @PrimaryKey("userInfoID")
+ * public interface UserInfo extends Storable<UserInfo> {
+ *     @Automatic
+ *     long getUserInfoID();
+ *     void setUserInfoID(long id);
+ *
+ *     ...
+ * }
+ * 
+ * + * @author Brian S O'Neill + * @see Sequence + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Automatic { +} diff --git a/src/main/java/com/amazon/carbonado/CorruptEncodingException.java b/src/main/java/com/amazon/carbonado/CorruptEncodingException.java index 1786cb8..48c22fa 100644 --- a/src/main/java/com/amazon/carbonado/CorruptEncodingException.java +++ b/src/main/java/com/amazon/carbonado/CorruptEncodingException.java @@ -58,6 +58,10 @@ public class CorruptEncodingException extends FetchException { * If the decoder can at least extract the primary key, it should set it here. */ public void setStorableWithPrimaryKey(Storable s) { + if (s != null) { + // Do this to ensure that primary key is known to be defined. + s.markAllPropertiesClean(); + } mStorable = s; } diff --git a/src/main/java/com/amazon/carbonado/Storage.java b/src/main/java/com/amazon/carbonado/Storage.java index 3489d63..75b9985 100644 --- a/src/main/java/com/amazon/carbonado/Storage.java +++ b/src/main/java/com/amazon/carbonado/Storage.java @@ -111,6 +111,16 @@ public interface Storage { */ Query query(Filter filter) throws FetchException; + /** + * Attempts to quickly delete all Storables instances in this + * Storage. Support for transactional truncation is not guaranteed. + * + *

If this Storage has any registered triggers which act on deletes, all + * Storables are deleted via {@code query().deleteAll()} instead to ensure + * these triggers get run. + */ + void truncate() throws PersistException; + /** * Register a trigger which will be called for overridden methods in the given * trigger implementation. The newly added trigger is invoked before and diff --git a/src/main/java/com/amazon/carbonado/cursor/FilteredCursor.java b/src/main/java/com/amazon/carbonado/cursor/FilteredCursor.java index c25f8db..faf990d 100644 --- a/src/main/java/com/amazon/carbonado/cursor/FilteredCursor.java +++ b/src/main/java/com/amazon/carbonado/cursor/FilteredCursor.java @@ -37,6 +37,27 @@ import com.amazon.carbonado.filter.OpenFilter; * @author Brian S O'Neill */ public abstract class FilteredCursor extends AbstractCursor { + /** + * Returns a Cursor that is filtered by the given filter expression and values. + * + * @param cursor cursor to wrap + * @param type type of storable + * @param filter filter to apply + * @param filterValues values for filter + * @return wrapped cursor which filters results + * @throws IllegalStateException if any values are not specified + * @throws IllegalArgumentException if any argument is null + */ + public static Cursor applyFilter(Cursor cursor, + Class type, + String filter, + Object... filterValues) + { + Filter f = Filter.filterFor(type, filter).bind(); + FilterValues fv = f.initialFilterValues().withValues(filterValues); + return applyFilter(f, fv, cursor); + } + /** * Returns a Cursor that is filtered by the given Filter and FilterValues. * The given Filter must be composed only of the same PropertyFilter @@ -61,6 +82,9 @@ public abstract class FilteredCursor extends AbstractCursor { throw new IllegalArgumentException(); } + // Make sure the filter is the same one that filterValues should be using. + filter = filter.bind(); + return FilteredCursorGenerator.getFactory(filter) .newFilteredCursor(cursor, filterValues.getValuesFor(filter)); } diff --git a/src/main/java/com/amazon/carbonado/info/ChainedProperty.java b/src/main/java/com/amazon/carbonado/info/ChainedProperty.java index 1c4022d..ab01c5f 100644 --- a/src/main/java/com/amazon/carbonado/info/ChainedProperty.java +++ b/src/main/java/com/amazon/carbonado/info/ChainedProperty.java @@ -164,6 +164,25 @@ public class ChainedProperty implements Appender { return getLastProperty().getType(); } + /** + * Returns true if any property in the chain can be null. + * + * @see com.amazon.carbonado.Nullable + */ + public boolean isNullable() { + if (mPrime.isNullable()) { + return true; + } + if (mChain != null) { + for (StorableProperty prop : mChain) { + if (prop.isNullable()) { + return true; + } + } + } + return false; + } + /** * Returns the last property in the chain, or the prime property if chain * is empty. @@ -293,7 +312,8 @@ public class ChainedProperty implements Appender { } /** - * Returns the chained property in a parseable form. + * Returns the chained property in a parseable form. The format is + * "name.subname.subsubname". */ @Override public String toString() { @@ -310,7 +330,8 @@ public class ChainedProperty implements Appender { } /** - * Appends the chained property in a parseable form. + * Appends the chained property in a parseable form. The format is + * "name.subname.subsubname". */ public void appendTo(Appendable app) throws IOException { app.append(mPrime.getName()); diff --git a/src/main/java/com/amazon/carbonado/info/StorableIntrospector.java b/src/main/java/com/amazon/carbonado/info/StorableIntrospector.java index 8be3d02..c527371 100644 --- a/src/main/java/com/amazon/carbonado/info/StorableIntrospector.java +++ b/src/main/java/com/amazon/carbonado/info/StorableIntrospector.java @@ -49,6 +49,7 @@ import org.cojen.util.WeakIdentityMap; import com.amazon.carbonado.Alias; import com.amazon.carbonado.AlternateKeys; import com.amazon.carbonado.Authoritative; +import com.amazon.carbonado.Automatic; import com.amazon.carbonado.FetchException; import com.amazon.carbonado.Index; import com.amazon.carbonado.Indexes; @@ -696,6 +697,7 @@ public class StorableIntrospector { Alias alias = null; Version version = null; Sequence sequence = null; + Automatic automatic = null; Independent independent = null; Join join = null; @@ -714,6 +716,7 @@ public class StorableIntrospector { alias = readMethod.getAnnotation(Alias.class); version = readMethod.getAnnotation(Version.class); sequence = readMethod.getAnnotation(Sequence.class); + automatic = readMethod.getAnnotation(Automatic.class); independent = readMethod.getAnnotation(Independent.class); join = readMethod.getAnnotation(Join.class); } @@ -745,6 +748,10 @@ public class StorableIntrospector { errorMessages.add ("Sequence annotation not allowed on mutator: " + writeMethod); } + if (writeMethod.getAnnotation(Automatic.class) != null) { + errorMessages.add + ("Automatic annotation not allowed on mutator: " + writeMethod); + } if (writeMethod.getAnnotation(Independent.class) != null) { errorMessages.add ("Independent annotation not allowed on mutator: " + writeMethod); @@ -820,7 +827,7 @@ public class StorableIntrospector { return new SimpleProperty (property, enclosing, nullable != null, pk, altKey, aliases, constraints, adapters == null ? null : adapters[0], - version != null, sequenceName, independent != null); + version != null, sequenceName, independent != null, automatic != null); } // Do additional work for join properties. @@ -928,7 +935,8 @@ public class StorableIntrospector { return new JoinProperty (property, enclosing, nullable != null, aliases, constraints, adapters == null ? null : adapters[0], - sequenceName, independent != null, joinedType, internal, external); + sequenceName, independent != null, automatic != null, + joinedType, internal, external); } private static StorablePropertyConstraint[] gatherConstraints @@ -1379,12 +1387,15 @@ public class StorableIntrospector { private final boolean mIsVersion; private final String mSequence; private final boolean mIndependent; + private final boolean mAutomatic; SimpleProperty(BeanProperty property, Class enclosing, boolean nullable, boolean primaryKey, boolean alternateKey, String[] aliases, StorablePropertyConstraint[] constraints, StorablePropertyAdapter adapter, - boolean isVersion, String sequence, boolean independent) { + boolean isVersion, String sequence, + boolean independent, boolean automatic) + { mBeanProperty = property; mEnclosingType = enclosing; mNullable = property.getType().isPrimitive() ? false : nullable; @@ -1396,6 +1407,7 @@ public class StorableIntrospector { mIsVersion = isVersion; mSequence = sequence; mIndependent = independent; + mAutomatic = automatic; } public final String getName() { @@ -1476,6 +1488,10 @@ public class StorableIntrospector { return mSequence; } + public final boolean isAutomatic() { + return mAutomatic; + } + public final boolean isIndependent() { return mIndependent; } @@ -1601,11 +1617,12 @@ public class StorableIntrospector { boolean nullable, String[] aliases, StorablePropertyConstraint[] constraints, StorablePropertyAdapter adapter, - String sequence, boolean independent, + String sequence, boolean independent, boolean automatic, Class joinedType, - String[] internal, String[] external) { + String[] internal, String[] external) + { super(property, enclosing, nullable, false, false, - aliases, constraints, adapter, false, sequence, independent); + aliases, constraints, adapter, false, sequence, independent, automatic); mJoinedType = joinedType; int length = internal.length; diff --git a/src/main/java/com/amazon/carbonado/info/StorableProperty.java b/src/main/java/com/amazon/carbonado/info/StorableProperty.java index c9cc8e1..ce374ac 100644 --- a/src/main/java/com/amazon/carbonado/info/StorableProperty.java +++ b/src/main/java/com/amazon/carbonado/info/StorableProperty.java @@ -184,9 +184,18 @@ public interface StorableProperty extends Appender { /** * Returns the property's sequence name, or null if none. + * + * @see com.amazon.carbonado.Sequence */ String getSequenceName(); + /** + * Returns true of this property is given an automatic value upon insert. + * + * @see com.amazon.carbonado.Automatic + */ + boolean isAutomatic(); + /** * Returns true if this property is the designated version number for the * Storable. diff --git a/src/main/java/com/amazon/carbonado/layout/StoredLayout.java b/src/main/java/com/amazon/carbonado/layout/StoredLayout.java index f7e250a..ea3c1ec 100644 --- a/src/main/java/com/amazon/carbonado/layout/StoredLayout.java +++ b/src/main/java/com/amazon/carbonado/layout/StoredLayout.java @@ -21,7 +21,6 @@ package com.amazon.carbonado.layout; import com.amazon.carbonado.Alias; import com.amazon.carbonado.AlternateKeys; import com.amazon.carbonado.Independent; -import com.amazon.carbonado.FetchException; import com.amazon.carbonado.Key; import com.amazon.carbonado.Nullable; import com.amazon.carbonado.PrimaryKey; diff --git a/src/main/java/com/amazon/carbonado/layout/StoredLayoutProperty.java b/src/main/java/com/amazon/carbonado/layout/StoredLayoutProperty.java index a6d25db..0777f10 100644 --- a/src/main/java/com/amazon/carbonado/layout/StoredLayoutProperty.java +++ b/src/main/java/com/amazon/carbonado/layout/StoredLayoutProperty.java @@ -20,7 +20,6 @@ package com.amazon.carbonado.layout; import com.amazon.carbonado.Alias; import com.amazon.carbonado.AlternateKeys; -import com.amazon.carbonado.FetchException; import com.amazon.carbonado.Independent; import com.amazon.carbonado.Key; import com.amazon.carbonado.Nullable; diff --git a/src/main/java/com/amazon/carbonado/raw/CustomStorableCodec.java b/src/main/java/com/amazon/carbonado/raw/CustomStorableCodec.java index 60bde78..cfb0883 100644 --- a/src/main/java/com/amazon/carbonado/raw/CustomStorableCodec.java +++ b/src/main/java/com/amazon/carbonado/raw/CustomStorableCodec.java @@ -232,6 +232,10 @@ public abstract class CustomStorableCodec implements Storabl private final int mPkPropertyCount; private final InstanceFactory mInstanceFactory; + // Modified by CustomStorableCodecFactory after construction. This provides + // backwards compatibility with implementations of CustomStorableCodecFactory. + RawSupport mSupport; + public interface InstanceFactory { Storable instantiate(RawSupport support, CustomStorableCodec codec); @@ -245,17 +249,40 @@ public abstract class CustomStorableCodec implements Storabl * @throws SupportException if Storable is not supported */ public CustomStorableCodec(Class type, boolean isMaster) throws SupportException { + this(type, isMaster, null); + } + + /** + * @param isMaster when true, version properties and sequences are managed + * @throws SupportException if Storable is not supported + */ + public CustomStorableCodec(Class type, boolean isMaster, RawSupport support) + throws SupportException + { mType = type; mPkPropertyCount = getPrimaryKeyIndex().getPropertyCount(); Class storableClass = getStorableClass(type, isMaster); mInstanceFactory = QuickConstructorGenerator .getInstance(storableClass, InstanceFactory.class); + mSupport = support; } public Class getStorableType() { return mType; } + @SuppressWarnings("unchecked") + public S instantiate() { + return (S) mInstanceFactory.instantiate(support(), this); + } + + @SuppressWarnings("unchecked") + public S instantiate(byte[] key, byte[] value) + throws FetchException + { + return (S) mInstanceFactory.instantiate(support(), key, value, this); + } + @SuppressWarnings("unchecked") public S instantiate(RawSupport support) { return (S) mInstanceFactory.instantiate(support, this); @@ -276,6 +303,18 @@ public abstract class CustomStorableCodec implements Storabl return encodePrimaryKey(values, 0, mPkPropertyCount); } + public RawSupport getSupport() { + return mSupport; + } + + private RawSupport support() { + RawSupport support = mSupport; + if (support == null) { + throw new IllegalStateException("No RawSupport"); + } + return support; + } + /** * Convenient access to all the storable properties. */ diff --git a/src/main/java/com/amazon/carbonado/raw/CustomStorableCodecFactory.java b/src/main/java/com/amazon/carbonado/raw/CustomStorableCodecFactory.java index bfba733..a3af33f 100644 --- a/src/main/java/com/amazon/carbonado/raw/CustomStorableCodecFactory.java +++ b/src/main/java/com/amazon/carbonado/raw/CustomStorableCodecFactory.java @@ -57,6 +57,30 @@ public abstract class CustomStorableCodecFactory implements StorableCodecFactory return createCodec(type, isMaster, layout); } + /** + * @param type type of storable to create codec for + * @param pkIndex ignored + * @param isMaster when true, version properties and sequences are managed + * @param layout when non-null, attempt to encode a storable layout + * generation value in each storable + * @param support binds generated storable with a storage layer + * @throws SupportException if type is not supported + */ + public CustomStorableCodec createCodec(Class type, + StorableIndex pkIndex, + boolean isMaster, + Layout layout, + RawSupport support) + throws SupportException + { + CustomStorableCodec codec = createCodec(type, isMaster, layout, support); + // Possibly set support after construction, for backwards compatibility. + if (codec.mSupport == null) { + codec.mSupport = support; + } + return codec; + } + /** * @param type type of storable to create codec for * @param isMaster when true, version properties and sequences are managed @@ -67,4 +91,20 @@ public abstract class CustomStorableCodecFactory implements StorableCodecFactory protected abstract CustomStorableCodec createCodec(Class type, boolean isMaster, Layout layout) throws SupportException; + + /** + * @param type type of storable to create codec for + * @param isMaster when true, version properties and sequences are managed + * @param layout when non-null, attempt to encode a storable layout + * generation value in each storable + * @param support binds generated storable with a storage layer + * @throws SupportException if type is not supported + */ + // Note: This factory method is not abstract for backwards compatibility. + protected CustomStorableCodec + createCodec(Class type, boolean isMaster, Layout layout, RawSupport support) + throws SupportException + { + return createCodec(type, isMaster, layout); + } } diff --git a/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java b/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java index 76622b7..c22881e 100644 --- a/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java +++ b/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java @@ -1318,8 +1318,11 @@ public class GenericEncodingStrategy { * Generates code to push RawSupport instance to the stack. RawSupport is * available only in Storable instances. If instanceVar is an Object[], a * SupportException is thrown. + * + * @param instanceVar Storable instance or array of property values. Null + * is storable instance of "this". */ - private void pushRawSupport(CodeAssembler a, LocalVariable instanceVar) + protected void pushRawSupport(CodeAssembler a, LocalVariable instanceVar) throws SupportException { boolean isObjectArrayInstanceVar = instanceVar != null diff --git a/src/main/java/com/amazon/carbonado/raw/GenericInstanceFactory.java b/src/main/java/com/amazon/carbonado/raw/GenericInstanceFactory.java index 339bdbd..fb2ef45 100644 --- a/src/main/java/com/amazon/carbonado/raw/GenericInstanceFactory.java +++ b/src/main/java/com/amazon/carbonado/raw/GenericInstanceFactory.java @@ -30,9 +30,7 @@ import com.amazon.carbonado.Storable; public interface GenericInstanceFactory { Storable instantiate(RawSupport support); - Storable instantiate(RawSupport support, byte[] key) - throws FetchException; + Storable instantiate(RawSupport support, byte[] key) throws FetchException; - Storable instantiate(RawSupport support, byte[] key, byte[] value) - throws FetchException; + Storable instantiate(RawSupport support, byte[] key, byte[] value) throws FetchException; } diff --git a/src/main/java/com/amazon/carbonado/raw/GenericStorableCodec.java b/src/main/java/com/amazon/carbonado/raw/GenericStorableCodec.java index 741a50d..0ef4846 100644 --- a/src/main/java/com/amazon/carbonado/raw/GenericStorableCodec.java +++ b/src/main/java/com/amazon/carbonado/raw/GenericStorableCodec.java @@ -79,6 +79,7 @@ public class GenericStorableCodec implements StorableCodec implements StorableCodec GenericStorableCodec getInstance (GenericStorableCodecFactory factory, - GenericEncodingStrategy encodingStrategy, boolean isMaster, Layout layout) + GenericEncodingStrategy encodingStrategy, boolean isMaster, + Layout layout, RawSupport support) throws SupportException { - Object key; - if (layout == null) { - key = KeyFactory.createKey(new Object[] {encodingStrategy, isMaster}); - } else { - key = KeyFactory.createKey - (new Object[] {encodingStrategy, isMaster, factory, layout.getGeneration()}); - } + Object key = KeyFactory.createKey(new Object[] {encodingStrategy, isMaster, layout}); - GenericStorableCodec codec = (GenericStorableCodec) cCache.get(key); - if (codec == null) { - codec = new GenericStorableCodec - (factory, - encodingStrategy.getType(), - generateStorable(encodingStrategy, isMaster, layout), - encodingStrategy, - layout); - cCache.put(key, codec); + Class storableImpl = (Class) cCache.get(key); + if (storableImpl == null) { + storableImpl = generateStorable(encodingStrategy, isMaster, layout); + cCache.put(key, storableImpl); } - return codec; + return new GenericStorableCodec + (factory, + encodingStrategy.getType(), + storableImpl, + encodingStrategy, + layout, + support); } @SuppressWarnings("unchecked") @@ -325,10 +322,7 @@ public class GenericStorableCodec implements StorableCodec mStorableClass; - // Weakly reference the encoding strategy because of the way - // GenericStorableCodec instances are cached in a SoftValuedHashMap. - // GenericStorableCodec can still be reclaimed by the garbage collector. - private final WeakReference> mEncodingStrategy; + private final GenericEncodingStrategy mEncodingStrategy; private final GenericInstanceFactory mInstanceFactory; @@ -339,27 +333,30 @@ public class GenericStorableCodec implements StorableCodec mSupport; + // Maps layout generations to Decoders. private IntHashMap mDecoders; private GenericStorableCodec(GenericStorableCodecFactory factory, Class type, Class storableClass, GenericEncodingStrategy encodingStrategy, - Layout layout) { + Layout layout, RawSupport support) + { mFactory = factory; mType = type; mStorableClass = storableClass; - mEncodingStrategy = new WeakReference>(encodingStrategy); + mEncodingStrategy = encodingStrategy; mInstanceFactory = QuickConstructorGenerator .getInstance(storableClass, GenericInstanceFactory.class); mPrimaryKeyFactory = getSearchKeyFactory(encodingStrategy.gatherAllKeyProperties()); mLayout = layout; + mSupport = support; if (layout != null) { try { // Assign static reference back to this codec. - Method m = storableClass.getMethod - (ASSIGN_CODEC_METHOD_NAME, WeakReference.class); + Method m = storableClass.getMethod(ASSIGN_CODEC_METHOD_NAME, WeakReference.class); m.invoke(null, new WeakReference(this)); } catch (Exception e) { ThrowUnchecked.fireFirstDeclaredCause(e); @@ -375,7 +372,30 @@ public class GenericStorableCodec implements StorableCodec implements StorableCodec support, byte[] key, byte[] value) - throws FetchException - { + public S instantiate(RawSupport support, byte[] key, byte[] value) throws FetchException { try { return (S) mInstanceFactory.instantiate(support, key, value); } catch (CorruptEncodingException e) { @@ -407,11 +426,11 @@ public class GenericStorableCodec implements StorableCodec getPrimaryKeyIndex() { - return getEncodingStrategy().getPrimaryKeyIndex(); + return mEncodingStrategy.getPrimaryKeyIndex(); } public int getPrimaryKeyPrefixLength() { - return getEncodingStrategy().getConstantKeyPrefixLength(); + return mEncodingStrategy.getConstantKeyPrefixLength(); } public byte[] encodePrimaryKey(S storable) { @@ -434,6 +453,18 @@ public class GenericStorableCodec implements StorableCodec getSupport() { + return mSupport; + } + + private RawSupport support() { + RawSupport support = mSupport; + if (support == null) { + throw new IllegalStateException("No RawSupport"); + } + return support; + } + /** * Returns a concrete Storable implementation, which is fully * thread-safe. It has two constructors defined: @@ -499,17 +530,8 @@ public class GenericStorableCodec implements StorableCodec getEncodingStrategy() { - // Should never be null, even though it is weakly referenced. As long - // as this class can be reached by the cache, the encoding strategy - // object exists since it is the cache key. - return mEncodingStrategy.get(); - } - @SuppressWarnings("unchecked") private SearchKeyFactory generateSearchKeyFactory(OrderedProperty[] properties) { - GenericEncodingStrategy encodingStrategy = getEncodingStrategy(); - ClassInjector ci; { StringBuilder b = new StringBuilder(); @@ -562,7 +584,7 @@ public class GenericStorableCodec implements StorableCodec implements StorableCodec implements StorableCodec implements StorableCodec implements StorableCodec implements StorableCodec GenericStorableCodec createCodec(Class type, + StorableIndex pkIndex, + boolean isMaster, + Layout layout, + RawSupport support) + throws SupportException { return GenericStorableCodec.getInstance - (this, createStrategy(type, pkIndex), isMaster, layout); + (this, createStrategy(type, pkIndex), isMaster, layout, support); } /** diff --git a/src/main/java/com/amazon/carbonado/raw/StorableCodec.java b/src/main/java/com/amazon/carbonado/raw/StorableCodec.java index 7965690..f9e347c 100644 --- a/src/main/java/com/amazon/carbonado/raw/StorableCodec.java +++ b/src/main/java/com/amazon/carbonado/raw/StorableCodec.java @@ -35,19 +35,36 @@ public interface StorableCodec { Class getStorableType(); /** - * Instantiate a Storable with no key or value defined yet. + * Instantiate a Storable with no key or value defined yet. The default + * {@link RawSupport} is supplied to the instance. + * + * @throws IllegalStateException if no default support exists + */ + S instantiate(); + + /** + * Instantiate a Storable with a specific key and value. The default + * {@link RawSupport} is supplied to the instance. + * + * @throws IllegalStateException if no default support exists + */ + S instantiate(byte[] key, byte[] value) throws FetchException; + + /** + * Instantiate a Storable with no key or value defined yet. Any + * {@link RawSupport} can be supplied to the instance. * * @param support binds generated storable with a storage layer */ S instantiate(RawSupport support); /** - * Instantiate a Storable with a specific key and value. + * Instantiate a Storable with a specific key and value. Any + * {@link RawSupport} can be supplied to the instance. * * @param support binds generated storable with a storage layer */ - S instantiate(RawSupport support, byte[] key, byte[] value) - throws FetchException; + S instantiate(RawSupport support, byte[] key, byte[] value) throws FetchException; /** * Returns the sequence and directions of properties that make up the @@ -113,4 +130,10 @@ public interface StorableCodec { * prefix. Returned value may be null if no prefix is defined. */ byte[] encodePrimaryKeyPrefix(); + + /** + * Returns the default {@link RawSupport} object that is supplied to + * Storable instances produced by this codec. + */ + RawSupport getSupport(); } diff --git a/src/main/java/com/amazon/carbonado/raw/StorableCodecFactory.java b/src/main/java/com/amazon/carbonado/raw/StorableCodecFactory.java index 1b19490..db05a9e 100644 --- a/src/main/java/com/amazon/carbonado/raw/StorableCodecFactory.java +++ b/src/main/java/com/amazon/carbonado/raw/StorableCodecFactory.java @@ -51,4 +51,20 @@ public interface StorableCodecFactory { boolean isMaster, Layout layout) throws SupportException; + + /** + * @param type type of storable to create codec for + * @param pkIndex suggested index for primary key (optional) + * @param isMaster when true, version properties and sequences are managed + * @param layout when non-null, attempt to encode a storable layout + * generation value in each storable + * @param support binds generated storable with a storage layer + * @throws SupportException if type is not supported + */ + StorableCodec createCodec(Class type, + StorableIndex pkIndex, + boolean isMaster, + Layout layout, + RawSupport support) + throws SupportException; } diff --git a/src/main/java/com/amazon/carbonado/repo/indexed/IndexEntryAccessor.java b/src/main/java/com/amazon/carbonado/repo/indexed/IndexEntryAccessor.java index b107a6e..0c1e6b8 100644 --- a/src/main/java/com/amazon/carbonado/repo/indexed/IndexEntryAccessor.java +++ b/src/main/java/com/amazon/carbonado/repo/indexed/IndexEntryAccessor.java @@ -20,7 +20,6 @@ package com.amazon.carbonado.repo.indexed; import java.util.Comparator; -import com.amazon.carbonado.FetchException; import com.amazon.carbonado.Storable; import com.amazon.carbonado.Storage; diff --git a/src/main/java/com/amazon/carbonado/repo/indexed/IndexEntryGenerator.java b/src/main/java/com/amazon/carbonado/repo/indexed/IndexEntryGenerator.java index 4059ac3..a126224 100644 --- a/src/main/java/com/amazon/carbonado/repo/indexed/IndexEntryGenerator.java +++ b/src/main/java/com/amazon/carbonado/repo/indexed/IndexEntryGenerator.java @@ -24,7 +24,6 @@ import java.util.WeakHashMap; import java.lang.ref.Reference; import java.lang.ref.SoftReference; -import com.amazon.carbonado.FetchException; import com.amazon.carbonado.Storable; import com.amazon.carbonado.SupportException; import com.amazon.carbonado.info.StorableIndex; diff --git a/src/main/java/com/amazon/carbonado/repo/indexed/IndexedRepository.java b/src/main/java/com/amazon/carbonado/repo/indexed/IndexedRepository.java index d558950..097185a 100644 --- a/src/main/java/com/amazon/carbonado/repo/indexed/IndexedRepository.java +++ b/src/main/java/com/amazon/carbonado/repo/indexed/IndexedRepository.java @@ -43,7 +43,7 @@ import com.amazon.carbonado.info.StorableIntrospector; import com.amazon.carbonado.qe.RepositoryAccess; import com.amazon.carbonado.qe.StorageAccess; -import com.amazon.carbonado.spi.StorageCollection; +import com.amazon.carbonado.spi.StoragePool; /** * Wraps another repository in order to make it support indexes. The wrapped @@ -62,20 +62,23 @@ class IndexedRepository implements Repository, private final String mName; private final boolean mIndexRepairEnabled; private final double mIndexThrottle; - private final StorageCollection mStorages; + private final boolean mAllClustered; + private final StoragePool mStoragePool; IndexedRepository(AtomicReference rootRef, String name, Repository repository, boolean indexRepairEnabled, - double indexThrottle) + double indexThrottle, + boolean allClustered) { mRootRef = rootRef; mRepository = repository; mName = name; mIndexRepairEnabled = indexRepairEnabled; mIndexThrottle = indexThrottle; + mAllClustered = allClustered; - mStorages = new StorageCollection() { + mStoragePool = new StoragePool() { protected Storage createStorage(Class type) throws RepositoryException { @@ -112,7 +115,7 @@ class IndexedRepository implements Repository, public Storage storageFor(Class type) throws MalformedTypeException, SupportException, RepositoryException { - return mStorages.storageFor(type); + return mStoragePool.get(type); } public Transaction enterTransaction() { @@ -221,4 +224,8 @@ class IndexedRepository implements Repository, double getIndexRepairThrottle() { return mIndexThrottle; } + + boolean isAllClustered() { + return mAllClustered; + } } diff --git a/src/main/java/com/amazon/carbonado/repo/indexed/IndexedRepositoryBuilder.java b/src/main/java/com/amazon/carbonado/repo/indexed/IndexedRepositoryBuilder.java index 4cd4afb..e7ad8ed 100644 --- a/src/main/java/com/amazon/carbonado/repo/indexed/IndexedRepositoryBuilder.java +++ b/src/main/java/com/amazon/carbonado/repo/indexed/IndexedRepositoryBuilder.java @@ -49,6 +49,7 @@ public class IndexedRepositoryBuilder extends AbstractRepositoryBuilder { private RepositoryBuilder mRepoBuilder; private boolean mIndexRepairEnabled = true; private double mIndexThrottle = 1.0; + private boolean mAllClustered; public IndexedRepositoryBuilder() { } @@ -75,7 +76,8 @@ public class IndexedRepositoryBuilder extends AbstractRepositoryBuilder { Repository repo = new IndexedRepository(rootRef, getName(), wrapped, isIndexRepairEnabled(), - getIndexRepairThrottle()); + getIndexRepairThrottle(), + isAllClustered()); rootRef.set(repo); return repo; } @@ -167,6 +169,23 @@ public class IndexedRepositoryBuilder extends AbstractRepositoryBuilder { mIndexThrottle = desiredSpeed; } + /** + * Returns true if all indexes should be identified as clustered. This + * affects how indexes are selected by the query analyzer. + */ + public boolean isAllClustered() { + return mAllClustered; + } + + /** + * When all indexes are identified as clustered, the query analyzer treats + * all indexes as performing equally well. This is suitable for indexing + * repositories that never read from a slow storage medium. + */ + public void setAllClustered(boolean clustered) { + mAllClustered = clustered; + } + public void errorCheck(Collection messages) throws ConfigurationException { super.errorCheck(messages); if (null == getWrappedRepository()) { diff --git a/src/main/java/com/amazon/carbonado/repo/indexed/IndexedStorage.java b/src/main/java/com/amazon/carbonado/repo/indexed/IndexedStorage.java index 7b66f50..fe0cfe8 100644 --- a/src/main/java/com/amazon/carbonado/repo/indexed/IndexedStorage.java +++ b/src/main/java/com/amazon/carbonado/repo/indexed/IndexedStorage.java @@ -30,6 +30,7 @@ import org.apache.commons.logging.LogFactory; import com.amazon.carbonado.Cursor; import com.amazon.carbonado.FetchException; import com.amazon.carbonado.IsolationLevel; +import com.amazon.carbonado.PersistException; import com.amazon.carbonado.Query; import com.amazon.carbonado.RepositoryException; import com.amazon.carbonado.Storable; @@ -188,6 +189,9 @@ class IndexedStorage implements Storage, StorageAccess // Assume index is malformed, so ignore it. continue; } + if (mRepository.isAllClustered()) { + freeIndex = freeIndex.clustered(true); + } mAllIndexInfoMap.put(freeIndex, ii); freeIndexSet.add(freeIndex); } @@ -210,6 +214,10 @@ class IndexedStorage implements Storage, StorageAccess // Add the indexes we get for free. queryableIndexSet.addAll(freeIndexSet); + + if (mRepository.isAllClustered()) { + queryableIndexSet.markClustered(true); + } } // The set of indexes that should be kept up-to-date. If index repair @@ -313,6 +321,36 @@ class IndexedStorage implements Storage, StorageAccess return mQueryEngine.query(filter); } + public void truncate() throws PersistException { + hasManagedIndexes: { + for (IndexInfo info : mAllIndexInfoMap.values()) { + if (info instanceof ManagedIndex) { + break hasManagedIndexes; + } + } + + // No managed indexes, so nothing special to do. + mMasterStorage.truncate(); + return; + } + + Transaction txn = mRepository.enterTransaction(); + try { + mMasterStorage.truncate(); + + // Now truncate the indexes. + for (IndexInfo info : mAllIndexInfoMap.values()) { + if (info instanceof ManagedIndex) { + ((ManagedIndex) info).getIndexEntryStorage().truncate(); + } + } + + txn.commit(); + } finally { + txn.exit(); + } + } + public boolean addTrigger(Trigger trigger) { return mMasterStorage.addTrigger(trigger); } @@ -372,9 +410,6 @@ class IndexedStorage implements Storage, StorageAccess } } - // FIXME: sort buffer should be on repository access. Also, create abstract - // repository access that creates the correct merge sort buffer. And more: - // create capability for managing merge sort buffers. return new MergeSortBuffer(mRootStorage); } @@ -521,20 +556,24 @@ class IndexedStorage implements Storage, StorageAccess { // Doesn't completely remove the index, but it should free up space. + double desiredSpeed = mRepository.getIndexRepairThrottle(); Throttle throttle = desiredSpeed < 1.0 ? new Throttle(POPULATE_THROTTLE_WINDOW) : null; - long totalDropped = 0; - while (true) { - Transaction txn = mRepository.getWrappedRepository() - .enterTopTransaction(IsolationLevel.READ_COMMITTED); - txn.setForUpdate(true); - try { - Cursor cursor = indexEntryStorage.query().fetch(); - if (!cursor.hasNext()) { - break; - } - int count = 0; + if (throttle == null) { + indexEntryStorage.truncate(); + } else { + long totalDropped = 0; + while (true) { + Transaction txn = mRepository.getWrappedRepository() + .enterTopTransaction(IsolationLevel.READ_COMMITTED); + txn.setForUpdate(true); + try { + Cursor cursor = indexEntryStorage.query().fetch(); + if (!cursor.hasNext()) { + break; + } + int count = 0; final long savedTotal = totalDropped; boolean anyFailure = false; try { @@ -544,26 +583,25 @@ class IndexedStorage implements Storage, StorageAccess } else { anyFailure = true; } + } + } finally { + cursor.close(); + } + txn.commit(); + if (log.isInfoEnabled()) { + log.info("Removed " + totalDropped + " index entries"); } - } finally { - cursor.close(); - } - txn.commit(); - if (log.isInfoEnabled()) { - log.info("Removed " + totalDropped + " index entries"); - } if (anyFailure && totalDropped <= savedTotal) { log.warn("No indexes removed in last batch. " + "Aborting index removal cleanup"); break; } - } catch (FetchException e) { - throw e.toPersistException(); - } finally { - txn.exit(); - } + } catch (FetchException e) { + throw e.toPersistException(); + } finally { + txn.exit(); + } - if (throttle != null) { try { throttle.throttle(desiredSpeed, POPULATE_THROTTLE_SLEEP_PRECISION); } catch (InterruptedException e) { diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCBlob.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCBlob.java index d84d033..ba5cdcf 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCBlob.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCBlob.java @@ -126,7 +126,7 @@ class JDBCBlob extends AbstractBlob implements JDBCLob { throw new FetchException("Blob value is null"); } try { - JDBCTransaction txn = mRepo.openTransactionManager().getTxn(); + JDBCTransaction txn = mRepo.localTxnManager().getTxn(); if (txn != null) { txn.register(this); } @@ -143,7 +143,7 @@ class JDBCBlob extends AbstractBlob implements JDBCLob { if ((mBlob = mLoader.load(mRepo)) == null) { throw new PersistException("Blob value is null"); } - JDBCTransaction txn = mRepo.openTransactionManager().getTxn(); + JDBCTransaction txn = mRepo.localTxnManager().getTxn(); if (txn != null) { txn.register(this); } diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCClob.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCClob.java index 70ce7f9..c4e74f1 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCClob.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCClob.java @@ -126,7 +126,7 @@ class JDBCClob extends AbstractClob implements JDBCLob { throw new FetchException("Clob value is null"); } try { - JDBCTransaction txn = mRepo.openTransactionManager().getTxn(); + JDBCTransaction txn = mRepo.localTxnManager().getTxn(); if (txn != null) { txn.register(this); } @@ -143,7 +143,7 @@ class JDBCClob extends AbstractClob implements JDBCLob { if ((mClob = mLoader.load(mRepo)) == null) { throw new PersistException("Clob value is null"); } - JDBCTransaction txn = mRepo.openTransactionManager().getTxn(); + JDBCTransaction txn = mRepo.localTxnManager().getTxn(); if (txn != null) { txn.register(this); } diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCCursor.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCCursor.java index 13c7548..28b73c7 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCCursor.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCCursor.java @@ -37,22 +37,27 @@ class JDBCCursor extends AbstractCursor { private final JDBCStorage mStorage; private final Connection mConnection; private final PreparedStatement mStatement; + private final boolean mScrollInsensitiveReadOnly; private ResultSet mResultSet; private boolean mHasNext; /** + * @param scrollInsensitiveReadOnly when true, statement is + * TYPE_SCROLL_INSENSITIVE and CONCUR_READ_ONLY. * @throws SQLException from executeQuery on statement. Caller must clean * up when this happens by closing statement and connection. */ JDBCCursor(JDBCStorage storage, Connection con, - PreparedStatement statement) + PreparedStatement statement, + boolean scrollInsensitiveReadOnly) throws SQLException { mStorage = storage; mConnection = con; mStatement = statement; + mScrollInsensitiveReadOnly = scrollInsensitiveReadOnly; mResultSet = statement.executeQuery(); } @@ -121,11 +126,49 @@ class JDBCCursor extends AbstractCursor { } int actual = 0; - while (amount > 0) { + + if (amount > 1 && mScrollInsensitiveReadOnly) { + // Skip a relative amount, which is preferred. if (hasNext()) { - actual++; - amount--; - mHasNext = false; + ResultSet rs = mResultSet; + try { + int rowStart = rs.getRow(); + mHasNext = rs.relative(amount); + int rowEnd = rs.getRow(); + if (rowEnd == 0) { + // Skipped past the end. Move back to find the last row number. + if (rs.previous() && (rowEnd = rs.getRow()) != 0) { + rowEnd++; + } else if (rs.last() && (rowEnd = rs.getRow()) != 0) { + rowEnd++; + } else { + // No clue how many were skipped. It's at least one. + rowEnd = rowStart + 1; + } + // Make sure ResultSet is closed below. + mHasNext = false; + } + actual += rowEnd - rowStart; + } catch (SQLException e) { + try { + close(); + } catch (FetchException e2) { + // Don't care. + } + throw mStorage.getJDBCRepository().toFetchException(e); + } + if (!mHasNext) { + close(); + } + } + } else { + // Call next a bunch, which is likely slower than relative skipping. + while (amount > 0) { + if (hasNext()) { + actual++; + amount--; + mHasNext = false; + } } } diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepository.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepository.java index 522bb2d..105de87 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepository.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepository.java @@ -18,45 +18,45 @@ package com.amazon.carbonado.repo.jdbc; +import java.lang.reflect.Method; import java.sql.Connection; import java.sql.DatabaseMetaData; +import java.sql.ResultSet; import java.sql.SQLException; - import java.util.ArrayList; -import java.util.Map; import java.util.IdentityHashMap; - +import java.util.Map; import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cojen.util.WeakIdentityMap; - import com.amazon.carbonado.FetchException; import com.amazon.carbonado.IsolationLevel; -import com.amazon.carbonado.Storage; -import com.amazon.carbonado.Storable; -import com.amazon.carbonado.SupportException; import com.amazon.carbonado.MalformedTypeException; import com.amazon.carbonado.PersistException; 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.TriggerFactory; import com.amazon.carbonado.UnsupportedTypeException; - -import com.amazon.carbonado.capability.Capability; import com.amazon.carbonado.capability.IndexInfo; import com.amazon.carbonado.capability.IndexInfoCapability; import com.amazon.carbonado.capability.ShutdownCapability; import com.amazon.carbonado.capability.StorableInfoCapability; - import com.amazon.carbonado.info.StorableProperty; - -import com.amazon.carbonado.spi.StorageCollection; +import com.amazon.carbonado.sequence.SequenceCapability; +import com.amazon.carbonado.sequence.SequenceValueProducer; +import com.amazon.carbonado.spi.AbstractRepository; +import com.amazon.carbonado.spi.TransactionManager; +import com.amazon.carbonado.util.ThrowUnchecked; /** * Repository implementation backed by a JDBC accessible database. @@ -66,16 +66,37 @@ import com.amazon.carbonado.spi.StorageCollection; * control precisely which tables and columns must be matched up. * * @author Brian S O'Neill + * @author bcastill * @see JDBCRepositoryBuilder */ // Note: this class must be public because auto-generated code needs access to it -public class JDBCRepository +public class JDBCRepository extends AbstractRepository implements Repository, IndexInfoCapability, ShutdownCapability, StorableInfoCapability, - JDBCConnectionCapability + JDBCConnectionCapability, + SequenceCapability { + /** + * Attempts to close a DataSource by searching for a "close" method. For + * some reason, there's no standard way to close a DataSource. + * + * @return false if DataSource doesn't have a close method. + */ + public static boolean closeDataSource(DataSource ds) throws SQLException { + try { + Method closeMethod = ds.getClass().getMethod("close"); + try { + closeMethod.invoke(ds); + } catch (Throwable e) { + ThrowUnchecked.fireFirstDeclaredCause(e, SQLException.class); + } + return true; + } catch (NoSuchMethodException e) { + return false; + } + } static IsolationLevel mapIsolationLevelFromJdbc(int jdbcLevel) { switch (jdbcLevel) { @@ -142,27 +163,26 @@ public class JDBCRepository private final Log mLog = LogFactory.getLog(getClass()); - private final String mName; final boolean mIsMaster; final Iterable mTriggerFactories; private final AtomicReference mRootRef; private final String mDatabaseProductName; private final DataSource mDataSource; + private final boolean mDataSourceClose; private final String mCatalog; private final String mSchema; - private final StorageCollection mStorages; + + // Maps Storable types which should have automatic version management. + private Map mAutoVersioningMap; // Track all open connections so that they can be closed when this // repository is closed. private Map mOpenConnections; - - private final ThreadLocal mCurrentTxnMgr; - - // Weakly tracks all JDBCTransactionManager instances for shutdown. - private final Map mAllTxnMgrs; + private final Lock mOpenConnectionsLock; private final boolean mSupportsSavepoints; private final boolean mSupportsSelectForUpdate; + private final boolean mSupportsScrollInsensitiveReadOnly; private final IsolationLevel mDefaultIsolationLevel; private final int mJdbcDefaultIsolationLevel; @@ -184,48 +204,42 @@ public class JDBCRepository * @param catalog optional catalog to search for tables -- actual meaning * is database independent * @param schema optional schema to search for tables -- actual meaning is - * database independent + * is database independent + * @param forceStoredSequence tells the repository to use a stored sequence + * even if the database supports native sequences */ @SuppressWarnings("unchecked") JDBCRepository(AtomicReference rootRef, String name, boolean isMaster, Iterable triggerFactories, - DataSource dataSource, String catalog, String schema) + DataSource dataSource, boolean dataSourceClose, + String catalog, String schema, + Map autoVersioningMap, + String sequenceSelectStatement, boolean forceStoredSequence) throws RepositoryException { - if (name == null || dataSource == null) { - throw new IllegalArgumentException(); + super(name); + if (dataSource == null) { + throw new IllegalArgumentException("DataSource cannot be null"); } - mName = name; mIsMaster = isMaster; mTriggerFactories = triggerFactories; mRootRef = rootRef; mDataSource = dataSource; + mDataSourceClose = dataSourceClose; mCatalog = catalog; mSchema = schema; - mStorages = new StorageCollection() { - protected Storage createStorage(Class type) - throws RepositoryException - { - // Lock on mAllTxnMgrs to prevent databases from being opened during shutdown. - synchronized (mAllTxnMgrs) { - JDBCStorableInfo info = examineStorable(type); - if (!info.isSupported()) { - throw new UnsupportedTypeException("Independent type not supported", type); - } - return new JDBCStorage(JDBCRepository.this, info); - } - } - }; + mAutoVersioningMap = autoVersioningMap; mOpenConnections = new IdentityHashMap(); - mCurrentTxnMgr = new ThreadLocal(); - mAllTxnMgrs = new WeakIdentityMap(); + mOpenConnectionsLock = new ReentrantLock(true); // Temporarily set to generic one, in case there's a problem during initialization. mExceptionTransformer = new JDBCExceptionTransformer(); + getLog().info("Opening repository \"" + getName() + '"'); + // Test connectivity and get some info on transaction isolation levels. Connection con = getConnection(); try { @@ -261,6 +275,8 @@ public class JDBCRepository mSupportsSavepoints = supportsSavepoints; mSupportsSelectForUpdate = md.supportsSelectForUpdate(); + mSupportsScrollInsensitiveReadOnly = md.supportsResultSetConcurrency + (ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); mJdbcDefaultIsolationLevel = md.getDefaultTransactionIsolation(); mDefaultIsolationLevel = mapIsolationLevelFromJdbc(mJdbcDefaultIsolationLevel); @@ -269,7 +285,6 @@ public class JDBCRepository mReadCommittedLevel = selectIsolationLevel(md, IsolationLevel.READ_COMMITTED); mRepeatableReadLevel = selectIsolationLevel(md, IsolationLevel.REPEATABLE_READ); mSerializableLevel = selectIsolationLevel(md, IsolationLevel.SERIALIZABLE); - } catch (SQLException e) { throw toRepositoryException(e); } finally { @@ -277,43 +292,27 @@ public class JDBCRepository } mSupportStrategy = JDBCSupportStrategy.createStrategy(this); + if (forceStoredSequence) { + mSupportStrategy.setSequenceSelectStatement(null); + } else if (sequenceSelectStatement != null && sequenceSelectStatement.length() > 0) { + mSupportStrategy.setSequenceSelectStatement(sequenceSelectStatement); + } + mSupportStrategy.setForceStoredSequence(forceStoredSequence); mExceptionTransformer = mSupportStrategy.createExceptionTransformer(); + + setAutoShutdownEnabled(true); } public DataSource getDataSource() { return mDataSource; } - public String getName() { - return mName; - } - - @SuppressWarnings("unchecked") - public Storage storageFor(Class type) throws RepositoryException { - return mStorages.storageFor(type); - } - - public Transaction enterTransaction() { - return openTransactionManager().enter(null); - } - - public Transaction enterTransaction(IsolationLevel level) { - return openTransactionManager().enter(level); - } - - public Transaction enterTopTransaction(IsolationLevel level) { - return openTransactionManager().enterTop(level); - } - - public IsolationLevel getTransactionIsolationLevel() { - return openTransactionManager().getIsolationLevel(); - } - /** * Returns true if a transaction is in progress and it is for update. */ + // Is called by auto-generated code and must be public. public boolean isTransactionForUpdate() { - return openTransactionManager().isForUpdate(); + return localTransactionManager().isForUpdate(); } /** @@ -334,14 +333,6 @@ public class JDBCRepository } } - @SuppressWarnings("unchecked") - public C getCapability(Class capabilityType) { - if (capabilityType.isInstance(this)) { - return (C) this; - } - return null; - } - public IndexInfo[] getIndexInfo(Class storableType) throws RepositoryException { @@ -352,7 +343,7 @@ public class JDBCRepository // We don't register Storable types persistently, so just return what // we know right now. ArrayList names = new ArrayList(); - for (Storage storage : mStorages.allStorage()) { + for (Storage storage : allStorage()) { names.add(storage.getStorableType().getName()); } return names.toArray(new String[names.size()]); @@ -401,72 +392,6 @@ public class JDBCRepository return jProperty; } - /** - * Returns the thread-local JDBCTransactionManager instance, creating it if - * needed. - */ - JDBCTransactionManager openTransactionManager() { - JDBCTransactionManager txnMgr = mCurrentTxnMgr.get(); - if (txnMgr == null) { - synchronized (mAllTxnMgrs) { - txnMgr = new JDBCTransactionManager(this); - mCurrentTxnMgr.set(txnMgr); - mAllTxnMgrs.put(txnMgr, null); - } - } - return txnMgr; - } - - public void close() { - shutdown(false); - } - - public boolean isAutoShutdownEnabled() { - return false; - } - - public void setAutoShutdownEnabled(boolean enabled) { - } - - public void shutdown() { - shutdown(true); - } - - private void shutdown(boolean suspendThreads) { - synchronized (mAllTxnMgrs) { - // Close transactions and cursors. - for (JDBCTransactionManager txnMgr : 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) { - getLog().error(null, e); - } - } - - // Now close all open connections. - if (mOpenConnections != null) { - for (Connection con : mOpenConnections.keySet()) { - try { - con.close(); - } catch (SQLException e) { - getLog().warn(null, e); - } - } - mOpenConnections = null; - } - } - } - - protected Log getLog() { - return mLog; - } - public String getDatabaseProductName() { return mDatabaseProductName; } @@ -482,22 +407,25 @@ public class JDBCRepository throw new FetchException("Repository is closed"); } - JDBCTransaction txn = openTransactionManager().getTxn(); + JDBCTransaction txn = localTransactionManager().getTxn(); if (txn != null) { // Return the connection used by the current transaction. return txn.getConnection(); } - // Get connection outside synchronized section since it may block. + // Get connection outside lock section since it may block. Connection con = mDataSource.getConnection(); con.setAutoCommit(true); - synchronized (mAllTxnMgrs) { + mOpenConnectionsLock.lock(); + try { if (mOpenConnections == null) { con.close(); throw new FetchException("Repository is closed"); } mOpenConnections.put(con, null); + } finally { + mOpenConnectionsLock.unlock(); } return con; @@ -515,7 +443,7 @@ public class JDBCRepository throw new FetchException("Repository is closed"); } - // Get connection outside synchronized section since it may block. + // Get connection outside lock section since it may block. Connection con = mDataSource.getConnection(); if (level == IsolationLevel.NONE) { @@ -527,12 +455,15 @@ public class JDBCRepository } } - synchronized (mAllTxnMgrs) { + mOpenConnectionsLock.lock(); + try { if (mOpenConnections == null) { con.close(); throw new FetchException("Repository is closed"); } mOpenConnections.put(con, null); + } finally { + mOpenConnectionsLock.unlock(); } return con; @@ -549,12 +480,15 @@ public class JDBCRepository public void yieldConnection(Connection con) throws FetchException { try { if (con.getAutoCommit()) { - synchronized (mAllTxnMgrs) { + mOpenConnectionsLock.lock(); + try { if (mOpenConnections != null) { mOpenConnections.remove(con); } + } finally { + mOpenConnectionsLock.unlock(); } - // Close connection outside synchronized section since it may block. + // Close connection outside lock section since it may block. if (con.getTransactionIsolation() != mJdbcDefaultIsolationLevel) { con.setTransactionIsolation(mJdbcDefaultIsolationLevel); } @@ -574,12 +508,15 @@ public class JDBCRepository * any exceptions too. */ private void forceYieldConnection(Connection con) { - synchronized (mAllTxnMgrs) { + mOpenConnectionsLock.lock(); + try { if (mOpenConnections != null) { mOpenConnections.remove(con); } + } finally { + mOpenConnectionsLock.unlock(); } - // Close connection outside synchronized section since it may block. + // Close connection outside lock section since it may block. try { con.close(); } catch (SQLException e) { @@ -595,6 +532,10 @@ public class JDBCRepository return mSupportsSelectForUpdate; } + boolean supportsScrollInsensitiveReadOnly() { + return mSupportsScrollInsensitiveReadOnly; + } + /** * Returns the highest supported level for the given desired level. * @@ -685,4 +626,80 @@ public class JDBCRepository JDBCExceptionTransformer getExceptionTransformer() { return mExceptionTransformer; } + + protected void shutdownHook() { + // Close all open connections. + mOpenConnectionsLock.lock(); + try { + if (mOpenConnections != null) { + for (Connection con : mOpenConnections.keySet()) { + try { + con.close(); + } catch (SQLException e) { + getLog().warn(null, e); + } + } + mOpenConnections = null; + } + } finally { + mOpenConnectionsLock.unlock(); + } + + if (mDataSourceClose) { + mLog.info("Closing DataSource: " + mDataSource); + try { + if (!closeDataSource(mDataSource)) { + mLog.info("DataSource doesn't have a close method: " + + mDataSource.getClass().getName()); + } + } catch (SQLException e) { + mLog.error("Failed to close DataSource", e); + } + } + } + + protected Log getLog() { + return mLog; + } + + protected TransactionManager createTransactionManager() { + return new JDBCTransactionManager(this); + } + + protected Storage createStorage(Class type) + throws RepositoryException + { + JDBCStorableInfo info = examineStorable(type); + if (!info.isSupported()) { + throw new UnsupportedTypeException("Independent type not supported", type); + } + + Boolean autoVersioning = false; + if (mAutoVersioningMap != null) { + autoVersioning = mAutoVersioningMap.get(type.getName()); + if (autoVersioning == null) { + // No explicit setting, so check wildcard setting. + autoVersioning = mAutoVersioningMap.get(null); + if (autoVersioning == null) { + autoVersioning = false; + } + } + } + + return new JDBCStorage(this, info, autoVersioning); + } + + protected SequenceValueProducer createSequenceValueProducer(String name) + throws RepositoryException + { + return mSupportStrategy.createSequenceValueProducer(name); + } + + /** + * Returns the thread-local JDBCTransactionManager, creating it if needed. + */ + // Provides access to transaction manager from other classes. + TransactionManager localTxnManager() { + return localTransactionManager(); + } } diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepositoryBuilder.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepositoryBuilder.java index 5b59852..b078e14 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepositoryBuilder.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepositoryBuilder.java @@ -20,6 +20,8 @@ package com.amazon.carbonado.repo.jdbc; import java.sql.SQLException; import java.util.Collection; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.atomic.AtomicReference; @@ -49,15 +51,18 @@ import com.amazon.carbonado.spi.AbstractRepositoryBuilder; *

  • {@link com.amazon.carbonado.capability.IndexInfoCapability IndexInfoCapability} *
  • {@link com.amazon.carbonado.capability.StorableInfoCapability StorableInfoCapability} *
  • {@link com.amazon.carbonado.capability.ShutdownCapability ShutdownCapability} + *
  • {@link com.amazon.carbonado.sequence.SequenceCapability SequenceCapability} *
  • {@link JDBCConnectionCapability JDBCConnectionCapability} * * * @author Brian S O'Neill + * @author bcastill */ public class JDBCRepositoryBuilder extends AbstractRepositoryBuilder { private String mName; private boolean mIsMaster = true; private DataSource mDataSource; + private boolean mDataSourceClose; private boolean mDataSourceLogging; private String mCatalog; private String mSchema; @@ -65,7 +70,10 @@ public class JDBCRepositoryBuilder extends AbstractRepositoryBuilder { private String mURL; private String mUsername; private String mPassword; - + private Map mAutoVersioningMap; + private String mSequenceSelectStatement; + private boolean mForceStoredSequence; + public JDBCRepositoryBuilder() { } @@ -73,7 +81,10 @@ public class JDBCRepositoryBuilder extends AbstractRepositoryBuilder { assertReady(); JDBCRepository repo = new JDBCRepository (rootRef, getName(), isMaster(), getTriggerFactories(), - getDataSource(), mCatalog, mSchema); + getDataSource(), getDataSourceCloseOnShutdown(), + mCatalog, mSchema, + getAutoVersioningMap(), + mSequenceSelectStatement, mForceStoredSequence); rootRef.set(repo); return repo; } @@ -137,6 +148,22 @@ public class JDBCRepositoryBuilder extends AbstractRepositoryBuilder { return ds; } + /** + * Pass true to cause the DataSource to be closed when the repository is + * closed or shutdown. By default, this option is false. + */ + public void setDataSourceCloseOnShutdown(boolean b) { + mDataSourceClose = b; + } + + /** + * Returns true if DataSource is closed when the repository is closed or + * shutdown. By default, this option is false. + */ + public boolean getDataSourceCloseOnShutdown() { + return mDataSourceClose; + } + /** * Pass true to enable debug logging. By default, it is false. * @@ -241,6 +268,64 @@ public class JDBCRepositoryBuilder extends AbstractRepositoryBuilder { return mPassword; } + /** + * By default, JDBCRepository assumes that {@link + * com.amazon.carbonado.Version version numbers} are initialized and + * incremented by triggers installed on the database. Enabling automatic + * versioning here causes the JDBCRepository to manage these operations + * itself. + * + * @param enabled true to enable, false to disable + * @param className name of Storable type to enable automatic version + * management on; pass null to enable all + */ + public void setAutoVersioningEnabled(boolean enabled, String className) { + if (mAutoVersioningMap == null) { + mAutoVersioningMap = new HashMap(); + } + mAutoVersioningMap.put(className, enabled); + } + + private Map getAutoVersioningMap() { + if (mAutoVersioningMap == null) { + return null; + } + return new HashMap(mAutoVersioningMap); + } + + /** + * Returns the native sequence select statement, which is null if the + * default is chosen. + */ + public String getSequenceSelectStatement() { + return mSequenceSelectStatement; + } + + /** + * Override the default native sequence select statement with a printf. + * For example, "SELECT %s.NEXTVAL FROM DUAL". + */ + public void setSequenceSelectStatement(String sequenceSelectStatement) { + mSequenceSelectStatement = sequenceSelectStatement; + } + + /** + * Returns true if native sequences should not be used. + */ + public boolean isForceStoredSequence() { + return mForceStoredSequence; + } + + /** + * By default, native sequences are used if supported. Otherwise, a table + * named "CARBONADO_SEQUENCE" or "CARBONADO_SEQUENCES" is used instead to + * hold sequence values. When forced, the table is always used instead of + * native sequences. + */ + public void setForceStoredSequence(boolean forceStoredSequence) { + mForceStoredSequence = forceStoredSequence; + } + public void errorCheck(Collection messages) throws ConfigurationException { super.errorCheck(messages); if (mDataSource == null) { diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCSequenceValueProducer.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCSequenceValueProducer.java index f98bd1e..e2fb9a8 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCSequenceValueProducer.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCSequenceValueProducer.java @@ -23,8 +23,7 @@ import java.sql.ResultSet; import java.sql.Statement; import com.amazon.carbonado.PersistException; - -import com.amazon.carbonado.spi.AbstractSequenceValueProducer; +import com.amazon.carbonado.sequence.AbstractSequenceValueProducer; /** * @@ -65,4 +64,8 @@ class JDBCSequenceValueProducer extends AbstractSequenceValueProducer { throw mRepo.toPersistException(e); } } + + public boolean returnReservedValues() { + return false; + } } diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableGenerator.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableGenerator.java index 7e09690..6a6a3b6 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableGenerator.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableGenerator.java @@ -40,6 +40,7 @@ import org.cojen.classfile.Modifiers; import org.cojen.classfile.Opcode; import org.cojen.classfile.TypeDesc; import org.cojen.util.ClassInjector; +import org.cojen.util.KeyFactory; import org.cojen.util.SoftValuedHashMap; import com.amazon.carbonado.FetchException; @@ -77,43 +78,57 @@ class JDBCStorableGenerator { // Initial StringBuilder capactity for update statement. private static final int INITIAL_UPDATE_BUFFER_SIZE = 100; - private static final Map, Class> cCache; + // Modes for automatic versioning when setting PreparedStatement values. + private static final int NORMAL = 0; + private static final int INITIAL_VERSION = 1; + private static final int INCREMENT_VERSION = 2; + + private static final Map> cCache; static { cCache = new SoftValuedHashMap(); } - static Class getGeneratedClass(JDBCStorableInfo info) + static Class getGeneratedClass(JDBCStorableInfo info, + boolean autoVersioning) throws SupportException { - Class type = info.getStorableType(); + Object key = KeyFactory.createKey(new Object[] {info, autoVersioning}); + synchronized (cCache) { - Class generatedClass = (Class) cCache.get(type); + Class generatedClass = (Class) cCache.get(key); if (generatedClass != null) { return generatedClass; } - generatedClass = new JDBCStorableGenerator(info).generateAndInjectClass(); - cCache.put(type, generatedClass); + generatedClass = + new JDBCStorableGenerator(info, autoVersioning).generateAndInjectClass(); + cCache.put(key, generatedClass); return generatedClass; } } private final Class mStorableType; private final JDBCStorableInfo mInfo; + private final boolean mAutoVersioning; private final Map> mAllProperties; private final ClassLoader mParentClassLoader; private final ClassInjector mClassInjector; private final ClassFile mClassFile; - private JDBCStorableGenerator(JDBCStorableInfo info) throws SupportException { + private JDBCStorableGenerator(JDBCStorableInfo info, boolean autoVersioning) + throws SupportException + { mStorableType = info.getStorableType(); mInfo = info; + mAutoVersioning = autoVersioning; mAllProperties = mInfo.getAllProperties(); EnumSet features = EnumSet .of(MasterFeature.INSERT_SEQUENCES, - MasterFeature.INSERT_TXN, MasterFeature.UPDATE_TXN); + MasterFeature.INSERT_CHECK_REQUIRED, // Must use @Automatic to override. + MasterFeature.INSERT_TXN, // Required because of reload after insert. + MasterFeature.UPDATE_TXN); // Required because of reload after update. final Class abstractClass = MasterStorableGenerator.getAbstractClass(mStorableType, features); @@ -127,7 +142,7 @@ class JDBCStorableGenerator { mClassFile.setTarget("1.5"); } - private Class generateAndInjectClass() { + private Class generateAndInjectClass() throws SupportException { // We'll need these "inner classes" which serve as Lob loading // callbacks. Lob's need to be reloaded if the original transaction has // been committed. @@ -417,58 +432,170 @@ class JDBCStorableGenerator { // Push connection in preparation for preparing a statement. b.loadLocal(conVar); - // Only insert version property if DIRTY. Create two insert - // statements, with and without the version property. - StringBuilder sb = new StringBuilder(); - createInsertStatement(sb, false); - String noVersion = sb.toString(); + String staticInsertStatement; + { + // Build the full static insert statement, even though it might + // not be used. If not used, then the length of the full static + // statement is used to determine the initial buffer size of + // the dynamically generated statement. + StringBuilder sb = new StringBuilder(); + + sb.append("INSERT INTO "); + sb.append(mInfo.getQualifiedTableName()); + sb.append(" ( "); + + int ordinal = 0; + for (JDBCStorableProperty property : mInfo.getAllProperties().values()) { + if (!property.isSelectable()) { + continue; + } + if (ordinal > 0) { + sb.append(','); + } + sb.append(property.getColumnName()); + ordinal++; + } - sb.setLength(0); - int versionPropNumber = createInsertStatement(sb, true); + sb.append(" ) VALUES ("); - LocalVariable includeVersion = null; + for (int i=0; i 0) { + sb.append(','); + } + sb.append('?'); + } + + sb.append(')'); - if (versionPropNumber < 0) { - // No version property at all, so no need to determine which - // statement to execute. - b.loadConstant(noVersion); + staticInsertStatement = sb.toString(); + } + + boolean useStaticInsertStatement = true; + for (JDBCStorableProperty property : mInfo.getAllProperties().values()) { + if (property.isVersion() || property.isAutomatic()) { + useStaticInsertStatement = false; + break; + } + } + + // Count of inserted properties when using dynamically generated statement. + LocalVariable insertCountVar = null; + + if (useStaticInsertStatement) { + // Load static insert statement to stack. + b.loadConstant(staticInsertStatement); } else { - includeVersion = b.createLocalVariable(null, TypeDesc.BOOLEAN); - - Label isDirty = b.createLabel(); - branchIfDirty(b, versionPropNumber, isDirty, true); - - // Version not dirty, so don't insert it. Assume database - // creates an initial version instead. - b.loadConstant(false); - b.storeLocal(includeVersion); - b.loadConstant(noVersion); - Label cont = b.createLabel(); - b.branch(cont); - - isDirty.setLocation(); - // Including version property in statement. - b.loadConstant(true); - b.storeLocal(includeVersion); - b.loadConstant(sb.toString()); - - cont.setLocation(); + // Dynamically build insert statement, ignoring automatic and + // version properties which are not DIRTY. + + insertCountVar = b.createLocalVariable(null, TypeDesc.INT); + int initialCount = 0; + for (JDBCStorableProperty property : mInfo.getAllProperties().values()) { + if (!property.isSelectable()) { + continue; + } + if (isAlwaysInserted(property)) { + // Don't bother dynamically counting properties which + // will always be inserted. + initialCount++; + } + } + + b.loadConstant(initialCount); + b.storeLocal(insertCountVar); + + TypeDesc stringBuilderType = TypeDesc.forClass(StringBuilder.class); + b.newObject(stringBuilderType); + b.dup(); + b.loadConstant(staticInsertStatement.length()); + b.invokeConstructor(stringBuilderType, new TypeDesc[] {TypeDesc.INT}); + + // Note extra space after left paren. This is required for case + // where no properties are explicitly inserted. The logic below + // to blindly delete the last character with a (thinking it is + // a comma) causes no harm when there are no properties. + b.loadConstant("INSERT INTO " + mInfo.getQualifiedTableName() + " ( "); + CodeBuilderUtil.callStringBuilderAppendString(b); + + int propNumber = -1; + for (JDBCStorableProperty property : mInfo.getAllProperties().values()) { + propNumber++; + if (!property.isSelectable()) { + continue; + } + + Label nextProperty = b.createLabel(); + if (!isAlwaysInserted(property)) { + // Property is set only if value manually supplied. + branchIfDirty(b, propNumber, nextProperty, false); + b.integerIncrement(insertCountVar, 1); + } + + // Append property name (with trailing comma) to StringBuilder. + b.loadConstant(property.getColumnName() + ','); + CodeBuilderUtil.callStringBuilderAppendString(b); + + nextProperty.setLocation(); + } + + // Blindly delete last character, assuming it is a trailing comma. + LocalVariable sbVar = b.createLocalVariable(null, stringBuilderType); + b.storeLocal(sbVar); + b.loadLocal(sbVar); + b.loadLocal(sbVar); + CodeBuilderUtil.callStringBuilderLength(b); + b.loadConstant(1); + b.math(Opcode.ISUB); + CodeBuilderUtil.callStringBuilderSetLength(b); + b.loadLocal(sbVar); // Load StringBuilder to stack as before. + + b.loadConstant(" ) VALUES ("); + CodeBuilderUtil.callStringBuilderAppendString(b); + + // Append all the necessary question marks. + b.loadLocal(insertCountVar); + Label finishStatement = b.createLabel(); + b.ifZeroComparisonBranch(finishStatement, "<="); + + b.loadConstant('?'); + CodeBuilderUtil.callStringBuilderAppendChar(b); + + Label loopStart = b.createLabel().setLocation(); + b.integerIncrement(insertCountVar, -1); + b.loadLocal(insertCountVar); + b.ifZeroComparisonBranch(finishStatement, "<="); + b.loadConstant(",?"); + CodeBuilderUtil.callStringBuilderAppendString(b); + b.branch(loopStart); + + finishStatement.setLocation(); + b.loadConstant(')'); + CodeBuilderUtil.callStringBuilderAppendChar(b); + CodeBuilderUtil.callStringBuilderToString(b); } - // At this point, the stack contains a connection and a SQL - // statement String. + // At this point, the stack contains a connection and a complete + // SQL insert statement String. + + // Determine if generated keys need to be retrieved. + Collection> identityProperties = + mInfo.getIdentityProperties().values(); LocalVariable psVar = b.createLocalVariable("ps", preparedStatementType); - b.invokeInterface(connectionType, "prepareStatement", preparedStatementType, - new TypeDesc[] {TypeDesc.STRING}); + if (identityProperties.size() == 0) { + b.invokeInterface(connectionType, "prepareStatement", preparedStatementType, + new TypeDesc[] {TypeDesc.STRING}); + } else { + b.loadConstant(Statement.RETURN_GENERATED_KEYS); + b.invokeInterface(connectionType, "prepareStatement", preparedStatementType, + new TypeDesc[] {TypeDesc.STRING, TypeDesc.INT}); + } b.storeLocal(psVar); Label tryAfterPs = b.createLabel().setLocation(); // Now fill in parameters with property values. - JDBCStorableProperty versionProperty = null; - // Gather all Lob properties to track if a post-insert update is required. Map, Integer> lobIndexMap = findLobs(); LocalVariable lobArrayVar = null; @@ -481,43 +608,85 @@ class JDBCStorableGenerator { } int ordinal = 0; + LocalVariable ordinalVar = null; + if (!useStaticInsertStatement) { + // Increment parameter ordinal at runtime. + ordinalVar = b.createLocalVariable(null, TypeDesc.INT); + b.loadConstant(0); + b.storeLocal(ordinalVar); + } + + int propNumber = -1; for (JDBCStorableProperty property : mAllProperties.values()) { + propNumber++; if (!property.isSelectable()) { continue; } - if (property.isVersion()) { - if (includeVersion != null) { - // Fill in version later, but check against boolean - // local variable to decide if it is was dirty. - versionProperty = property; - } - continue; - } + Label nextProperty = b.createLabel(); + if (!isAlwaysInserted(property)) { + // Property is set only if value manually supplied. + branchIfDirty(b, propNumber, nextProperty, false); + } + b.loadLocal(psVar); - b.loadConstant(++ordinal); + if (ordinalVar == null) { + b.loadConstant(++ordinal); + } else { + b.integerIncrement(ordinalVar, 1); + b.loadLocal(ordinalVar); + } - setPreparedStatementValue - (b, property, repoVar, null, lobArrayVar, lobIndexMap.get(property)); - } + Label setNormally = b.createLabel(); + if (property.isVersion() && mAutoVersioning) { + // Automatically supply initial value unless manually supplied. + branchIfDirty(b, propNumber, setNormally, true); + setPreparedStatementValue + (b, property, INITIAL_VERSION, + repoVar, null, lobArrayVar, lobIndexMap.get(property)); + b.branch(nextProperty); + } - if (versionProperty != null) { - // Fill in version property now, but only if was dirty. - b.loadLocal(includeVersion); - Label skipVersion = b.createLabel(); - b.ifZeroComparisonBranch(skipVersion, "=="); + setNormally.setLocation(); - b.loadLocal(psVar); - b.loadConstant(++ordinal); - setPreparedStatementValue(b, versionProperty, repoVar, null, null, null); + setPreparedStatementValue + (b, property, NORMAL, repoVar, null, lobArrayVar, lobIndexMap.get(property)); - skipVersion.setLocation(); + nextProperty.setLocation(); } // Execute the statement. b.loadLocal(psVar); b.invokeInterface(preparedStatementType, "executeUpdate", TypeDesc.INT, null); b.pop(); + + if (identityProperties.size() > 0) { + // Get the generated keys and set the properties. + b.loadLocal(psVar); + b.invokeInterface(preparedStatementType, "getGeneratedKeys", resultSetType, null); + + LocalVariable rsVar = b.createLocalVariable("rs", resultSetType); + b.storeLocal(rsVar); + Label tryAfterRs = b.createLabel().setLocation(); + + b.loadLocal(rsVar); + b.invokeInterface(resultSetType, "next", TypeDesc.BOOLEAN, null); + Label noResults = b.createLabel(); + b.ifZeroComparisonBranch(noResults, "=="); + + // Set property value. + LocalVariable initialOffsetVar = b.createLocalVariable(null, TypeDesc.INT); + b.loadConstant(1); + b.storeLocal(initialOffsetVar); + defineExtract(b, rsVar, initialOffsetVar, null, // no lobArrayVar + mInfo.getIdentityProperties().values(), + lobLoaderMap); + + noResults.setLocation(); + + closeResultSet(b, rsVar, tryAfterRs); + } + closeStatement(b, psVar, tryAfterPs); // Immediately reload object, to ensure that any database supplied @@ -573,18 +742,6 @@ class JDBCStorableGenerator { b.loadConstant(INITIAL_UPDATE_BUFFER_SIZE); b.invokeConstructor(stringBuilderType, new TypeDesc[] {TypeDesc.INT}); - // Methods on StringBuilder. - final Method appendStringMethod; - final Method appendCharMethod; - final Method toStringMethod; - try { - appendStringMethod = StringBuilder.class.getMethod("append", String.class); - appendCharMethod = StringBuilder.class.getMethod("append", char.class); - toStringMethod = StringBuilder.class.getMethod("toString", (Class[]) null); - } catch (NoSuchMethodException e) { - throw new UndeclaredThrowableException(e); - } - { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("UPDATE "); @@ -592,7 +749,8 @@ class JDBCStorableGenerator { sqlBuilder.append(" SET "); b.loadConstant(sqlBuilder.toString()); - b.invoke(appendStringMethod); // method leaves StringBuilder on stack + // Method leaves StringBuilder on stack. + CodeBuilderUtil.callStringBuilderAppendString(b); } // Iterate over the properties, appending a set parameter for each @@ -607,31 +765,36 @@ class JDBCStorableGenerator { propNumber++; if (property.isSelectable() && !property.isPrimaryKeyMember()) { - if (property.isVersion()) { - // TODO: Support option where version property is - // updated on the Carbonado side rather than relying on - // SQL trigger. + // Assume database trigger manages version. + if (property.isVersion() && !mAutoVersioning) { continue; } - Label isNotDirty = b.createLabel(); - branchIfDirty(b, propNumber, isNotDirty, false); + Label isNotDirty = null; + if (!property.isVersion()) { + // Version must always be updated, but all other + // properties are updated only if dirty. + isNotDirty = b.createLabel(); + branchIfDirty(b, propNumber, isNotDirty, false); + } b.loadLocal(countVar); Label isZero = b.createLabel(); b.ifZeroComparisonBranch(isZero, "=="); b.loadConstant(','); - b.invoke(appendCharMethod); + CodeBuilderUtil.callStringBuilderAppendChar(b); isZero.setLocation(); b.loadConstant(property.getColumnName()); - b.invoke(appendStringMethod); + CodeBuilderUtil.callStringBuilderAppendString(b); b.loadConstant("=?"); - b.invoke(appendStringMethod); + CodeBuilderUtil.callStringBuilderAppendString(b); b.integerIncrement(countVar, 1); - isNotDirty.setLocation(); + if (isNotDirty != null) { + isNotDirty.setLocation(); + } } } @@ -655,36 +818,36 @@ class JDBCStorableGenerator { b.ifZeroComparisonBranch(notZero, "!="); b.loadConstant(whereProperties.iterator().next().getColumnName()); - b.invoke(appendStringMethod); + CodeBuilderUtil.callStringBuilderAppendString(b); b.loadConstant("=?"); - b.invoke(appendStringMethod); + CodeBuilderUtil.callStringBuilderAppendString(b); notZero.setLocation(); } b.loadConstant(" WHERE "); - b.invoke(appendStringMethod); + CodeBuilderUtil.callStringBuilderAppendString(b); int ordinal = 0; for (JDBCStorableProperty property : whereProperties) { if (ordinal > 0) { b.loadConstant(" AND "); - b.invoke(appendStringMethod); + CodeBuilderUtil.callStringBuilderAppendString(b); } b.loadConstant(property.getColumnName()); - b.invoke(appendStringMethod); + CodeBuilderUtil.callStringBuilderAppendString(b); if (property.isNullable()) { - // FIXME + // TODO: Support null primary key or version property. Is this possible? throw new UnsupportedOperationException(); } else { b.loadConstant("=?"); - b.invoke(appendStringMethod); + CodeBuilderUtil.callStringBuilderAppendString(b); } ordinal++; } // Convert StringBuilder value to a String. - b.invoke(toStringMethod); + CodeBuilderUtil.callStringBuilderToString(b); // At this point, the stack contains a connection and a SQL // statement String. @@ -724,7 +887,7 @@ class JDBCStorableGenerator { b.loadLocal(psVar); b.loadLocal(indexVar); setPreparedStatementValue - (b, property, repoVar, null, lobArrayVar, lobIndexMap.get(property)); + (b, property, NORMAL, repoVar, null, lobArrayVar, lobIndexMap.get(property)); b.integerIncrement(indexVar, 1); @@ -736,24 +899,30 @@ class JDBCStorableGenerator { propNumber++; if (property.isSelectable() && !property.isPrimaryKeyMember()) { - if (property.isVersion()) { - // TODO: Support option where version property is - // updated on the Carbonado side rather than relying on - // SQL trigger. Just add one to the value. + // Assume database trigger manages version. + if (property.isVersion() && !mAutoVersioning) { continue; } - Label isNotDirty = b.createLabel(); - branchIfDirty(b, propNumber, isNotDirty, false); + Label isNotDirty = null; + if (!property.isVersion()) { + // Version must always be updated, but all other + // properties are updated only if dirty. + isNotDirty = b.createLabel(); + branchIfDirty(b, propNumber, isNotDirty, false); + } b.loadLocal(psVar); b.loadLocal(indexVar); setPreparedStatementValue - (b, property, repoVar, null, lobArrayVar, lobIndexMap.get(property)); + (b, property, property.isVersion() ? INCREMENT_VERSION : NORMAL, + repoVar, null, lobArrayVar, lobIndexMap.get(property)); b.integerIncrement(indexVar, 1); - isNotDirty.setLocation(); + if (isNotDirty != null) { + isNotDirty.setLocation(); + } } } @@ -762,12 +931,12 @@ class JDBCStorableGenerator { for (JDBCStorableProperty property : whereProperties) { if (property.isNullable()) { - // FIXME + // TODO: Support null primary key or version property. Is this possible? throw new UnsupportedOperationException(); } else { b.loadLocal(psVar); b.loadLocal(indexVar); - setPreparedStatementValue(b, property, repoVar, null, null, null); + setPreparedStatementValue(b, property, NORMAL, repoVar, null, null, null); } b.integerIncrement(indexVar, 1); @@ -923,6 +1092,13 @@ class JDBCStorableGenerator { return generatedClass; } + /** + * Returns true if property value is always part of insert statement. + */ + private boolean isAlwaysInserted(JDBCStorableProperty property) { + return property.isVersion() ? mAutoVersioning : !property.isAutomatic(); + } + /** * Finds all Lob properties and maps them to a zero-based index. This * information is used to update large Lobs after an insert or update. @@ -1037,6 +1213,7 @@ class JDBCStorableGenerator { LocalVariable psVar, LocalVariable jdbcRepoVar, LocalVariable instanceVar) + throws SupportException { final TypeDesc superType = TypeDesc.forClass(mClassFile.getSuperClassName()); final Iterable> properties = @@ -1108,28 +1285,19 @@ class JDBCStorableGenerator { b.loadConstant(capacity); b.invokeConstructor(stringBuilderType, new TypeDesc[] {TypeDesc.INT}); - // Methods on StringBuilder. - final Method appendStringMethod; - final Method toStringMethod; - try { - appendStringMethod = StringBuilder.class.getMethod("append", String.class); - toStringMethod = StringBuilder.class.getMethod("toString", (Class[]) null); - } catch (NoSuchMethodException e) { - throw new UndeclaredThrowableException(e); - } - b.loadConstant(sqlBuilder.toString()); - b.invoke(appendStringMethod); // method leaves StringBuilder on stack + // Method leaves StringBuilder on stack. + CodeBuilderUtil.callStringBuilderAppendString(b); ordinal = 0; for (JDBCStorableProperty property : nullableProperties) { if (ordinal > 0) { b.loadConstant(" AND "); - b.invoke(appendStringMethod); + CodeBuilderUtil.callStringBuilderAppendString(b); } b.loadConstant(property.getColumnName()); - b.invoke(appendStringMethod); + CodeBuilderUtil.callStringBuilderAppendString(b); b.loadThis(); @@ -1139,13 +1307,13 @@ class JDBCStorableGenerator { Label notNull = b.createLabel(); b.ifNullBranch(notNull, false); b.loadConstant("IS NULL"); - b.invoke(appendStringMethod); + CodeBuilderUtil.callStringBuilderAppendString(b); Label next = b.createLabel(); b.branch(next); notNull.setLocation(); b.loadConstant("=?"); - b.invoke(appendStringMethod); + CodeBuilderUtil.callStringBuilderAppendString(b); next.setLocation(); ordinal++; @@ -1160,13 +1328,13 @@ class JDBCStorableGenerator { b.ifZeroComparisonBranch(notForUpdate, "=="); b.loadConstant(" FOR UPDATE"); - b.invoke(appendStringMethod); + CodeBuilderUtil.callStringBuilderAppendString(b); notForUpdate.setLocation(); } // Convert StringBuilder to String. - b.invoke(toStringMethod); + CodeBuilderUtil.callStringBuilderToString(b); } // At this point, the stack contains a connection and a SQL statement String. @@ -1186,7 +1354,7 @@ class JDBCStorableGenerator { continue; } - Label skipProperty = b.createLabel(); + Label nextProperty = b.createLabel(); final TypeDesc propertyType = TypeDesc.forClass(property.getType()); @@ -1200,12 +1368,12 @@ class JDBCStorableGenerator { // was appended earlier with "IS NULL". b.loadThis(); b.loadField(superType, property.getName(), propertyType); - b.ifNullBranch(skipProperty, true); + b.ifNullBranch(nextProperty, true); } - setPreparedStatementValue(b, property, null, instanceVar, null, null); + setPreparedStatementValue(b, property, NORMAL, null, instanceVar, null, null); - skipProperty.setLocation(); + nextProperty.setLocation(); } return tryAfterPs; @@ -1223,6 +1391,7 @@ class JDBCStorableGenerator { * the original lob. An update statement needs to be issued after the load * to insert/update the large value. * + * @param mode one of NORMAL, INITIAL_VERSION or INCREMENT_VERSION * @param instanceVar when null, assume properties are contained in * "this". Otherwise, invoke property access methods on storable referenced * in var. @@ -1230,28 +1399,39 @@ class JDBCStorableGenerator { * @param lobIndex optional, used for lob properties */ private void setPreparedStatementValue - (CodeBuilder b, JDBCStorableProperty property, LocalVariable repoVar, + (CodeBuilder b, + JDBCStorableProperty property, + int mode, + LocalVariable repoVar, LocalVariable instanceVar, - LocalVariable lobArrayVar, Integer lobIndex) + LocalVariable lobArrayVar, + Integer lobIndex) + throws SupportException { - if (instanceVar == null) { - b.loadThis(); - } else { - b.loadLocal(instanceVar); - } - Class psClass = property.getPreparedStatementSetMethod().getParameterTypes()[1]; TypeDesc psType = TypeDesc.forClass(psClass); TypeDesc propertyType = TypeDesc.forClass(property.getType()); - StorablePropertyAdapter adapter = property.getAppliedAdapter(); - TypeDesc fromType; - if (adapter == null) { - // Get protected field directly, since no adapter. + + if (mode != INITIAL_VERSION) { + // Load storable to extract property value from. if (instanceVar == null) { - b.loadField(property.getName(), propertyType); + b.loadThis(); } else { - b.loadField(instanceVar.getType(), property.getName(), propertyType); + b.loadLocal(instanceVar); + } + } + + TypeDesc fromType; + + if (adapter == null) { + if (mode != INITIAL_VERSION) { + // Get protected field directly, since no adapter. + if (instanceVar == null) { + b.loadField(property.getName(), propertyType); + } else { + b.loadField(instanceVar.getType(), property.getName(), propertyType); + } } fromType = propertyType; } else { @@ -1263,21 +1443,27 @@ class JDBCStorableGenerator { } Method adaptMethod = adapter.findAdaptMethod(property.getType(), toClass); TypeDesc adaptType = TypeDesc.forClass(adaptMethod.getReturnType()); - // Invoke special inherited protected method that gets the field - // and invokes the adapter. Method was generated by - // StorableGenerator. - String methodName = property.getReadMethodName() + '$'; - if (instanceVar == null) { - b.invokeVirtual(methodName, adaptType, null); - } else { - b.invokeVirtual (instanceVar.getType(), methodName, adaptType, null); + if (mode != INITIAL_VERSION) { + // Invoke special inherited protected method that gets the field + // and invokes the adapter. Method was generated by + // StorableGenerator. + String methodName = property.getReadMethodName() + '$'; + if (instanceVar == null) { + b.invokeVirtual(methodName, adaptType, null); + } else { + b.invokeVirtual (instanceVar.getType(), methodName, adaptType, null); + } } fromType = adaptType; } Label done = b.createLabel(); - if (!fromType.isPrimitive()) { + if (mode == INITIAL_VERSION) { + CodeBuilderUtil.initialVersion(b, fromType, 1); + } else if (mode == INCREMENT_VERSION) { + CodeBuilderUtil.incrementVersion(b, fromType); + } else if (!fromType.isPrimitive()) { // Handle case where property value is null. b.dup(); Label notNull = b.createLabel(); @@ -1695,65 +1881,9 @@ class JDBCStorableGenerator { } } - /** - * @param b builder to receive statement - * @param withVersion when false, ignore any version property - * @return version property number, or -1 if none - */ - private int createInsertStatement(StringBuilder b, boolean withVersion) { - b.append("INSERT INTO "); - b.append(mInfo.getQualifiedTableName()); - b.append(" ("); - - JDBCStorableProperty versionProperty = null; - int versionPropNumber = -1; - - int ordinal = 0; - int propNumber = -1; - for (JDBCStorableProperty property : mInfo.getAllProperties().values()) { - propNumber++; - if (!property.isSelectable()) { - continue; - } - if (property.isVersion()) { - if (withVersion) { - versionProperty = property; - versionPropNumber = propNumber; - } - continue; - } - if (ordinal > 0) { - b.append(','); - } - b.append(property.getColumnName()); - ordinal++; - } - - // Insert version property at end, to make things easier for when the - // proper insert statement is selected. - if (versionProperty != null) { - if (ordinal > 0) { - b.append(','); - } - b.append(versionProperty.getColumnName()); - ordinal++; - } - - b.append(") VALUES ("); - - for (int i=0; i 0) { - b.append(','); - } - b.append('?'); - } - - b.append(')'); - - return versionPropNumber; - } - - private Map, Class> generateLobLoaders() { + private Map, Class> generateLobLoaders() + throws SupportException + { Map, Class> lobLoaderMap = new IdentityHashMap, Class>(); @@ -1789,7 +1919,9 @@ class JDBCStorableGenerator { * * @param loaderType either JDBCBlobLoader or JDBCClobLoader */ - private Class generateLobLoader(JDBCStorableProperty property, Class loaderType) { + private Class generateLobLoader(JDBCStorableProperty property, Class loaderType) + throws SupportException + { ClassInjector ci = ClassInjector.create (property.getEnclosingType().getName(), mParentClassLoader); diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableInfo.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableInfo.java index 6275c09..109725c 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableInfo.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableInfo.java @@ -71,5 +71,11 @@ public interface JDBCStorableInfo extends StorableInfo { Map> getDataProperties(); + /** + * Returns auto-increment properties which are primary key members. The map + * should almost always be empty or contain one property. + */ + Map> getIdentityProperties(); + JDBCStorableProperty getVersionProperty(); } diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableIntrospector.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableIntrospector.java index 987c809..aa17f11 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableIntrospector.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableIntrospector.java @@ -25,7 +25,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.LinkedHashMap; -import java.util.List; import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -56,7 +55,6 @@ import com.amazon.carbonado.RepositoryException; import com.amazon.carbonado.Storable; import com.amazon.carbonado.SupportException; -import com.amazon.carbonado.info.Direction; import com.amazon.carbonado.info.OrderedProperty; import com.amazon.carbonado.info.StorableInfo; import com.amazon.carbonado.info.StorableIntrospector; @@ -66,8 +64,6 @@ import com.amazon.carbonado.info.StorableProperty; import com.amazon.carbonado.info.StorablePropertyAdapter; import com.amazon.carbonado.info.StorablePropertyConstraint; -import com.amazon.carbonado.spi.IndexInfoImpl; - /** * Provides additional metadata for a {@link Storable} type needed by * JDBCRepository. The storable type must match to a table in an external @@ -335,7 +331,31 @@ public class JDBCStorableIntrospector extends StorableIntrospector { } } + boolean autoIncrement = mainProperty.isAutomatic(); + if (autoIncrement) { + // Need to execute a little query to check if column is + // auto-increment or not. This information is not available in + // the regular database metadata prior to jdk1.6. + + PreparedStatement ps = con.prepareStatement + ("SELECT " + columnInfo.columnName + + " FROM " + tableName + + " WHERE 1=0"); + + try { + ResultSet rs = ps.executeQuery(); + try { + autoIncrement = rs.getMetaData().isAutoIncrement(1); + } finally { + rs.close(); + } + } finally { + ps.close(); + } + } + jProperty = new JProperty(mainProperty, columnInfo, + autoIncrement, accessInfo.mResultSetGet, accessInfo.mPreparedStatementSet, accessInfo.getAdapter()); @@ -974,6 +994,7 @@ public class JDBCStorableIntrospector extends StorableIntrospector { private transient Map> mPrimaryKeyProperties; private transient Map> mDataProperties; + private transient Map> mIdentityProperties; private transient JDBCStorableProperty mVersionProperty; JInfo(StorableInfo mainInfo, @@ -1104,6 +1125,23 @@ public class JDBCStorableIntrospector extends StorableIntrospector { return mDataProperties; } + public Map> getIdentityProperties() { + if (mIdentityProperties == null) { + Map> idProps = + new LinkedHashMap>(1); + for (Map.Entry> entry : + getPrimaryKeyProperties().entrySet()) + { + JDBCStorableProperty property = entry.getValue(); + if (property.isAutoIncrement()) { + idProps.put(entry.getKey(), property); + } + } + mIdentityProperties = Collections.unmodifiableMap(idProps); + } + return mIdentityProperties; + } + public JDBCStorableProperty getVersionProperty() { if (mVersionProperty == null) { for (JDBCStorableProperty property : mAllProperties.values()) { @@ -1133,6 +1171,7 @@ public class JDBCStorableIntrospector extends StorableIntrospector { private final Integer mDecimalDigits; private final Integer mCharOctetLength; private final Integer mOrdinalPosition; + private final boolean mAutoIncrement; private JDBCStorableProperty[] mInternal; private JDBCStorableProperty[] mExternal; @@ -1140,9 +1179,13 @@ public class JDBCStorableIntrospector extends StorableIntrospector { /** * Join properties need to be filled in later. */ - JProperty(StorableProperty mainProperty, ColumnInfo columnInfo, - Method resultSetGet, Method preparedStatementSet, - StorablePropertyAdapter adapter) { + JProperty(StorableProperty mainProperty, + ColumnInfo columnInfo, + boolean autoIncrement, + Method resultSetGet, + Method preparedStatementSet, + StorablePropertyAdapter adapter) + { mMainProperty = mainProperty; mColumnName = columnInfo.columnName; mDataType = columnInfo.dataType; @@ -1154,6 +1197,7 @@ public class JDBCStorableIntrospector extends StorableIntrospector { mDecimalDigits = columnInfo.decimalDigits; mCharOctetLength = columnInfo.charOctetLength; mOrdinalPosition = columnInfo.ordinalPosition; + mAutoIncrement = autoIncrement; } JProperty(StorableProperty mainProperty) { @@ -1168,6 +1212,7 @@ public class JDBCStorableIntrospector extends StorableIntrospector { mDecimalDigits = null; mCharOctetLength = null; mOrdinalPosition = null; + mAutoIncrement = false; } public String getName() { @@ -1258,6 +1303,10 @@ public class JDBCStorableIntrospector extends StorableIntrospector { return mMainProperty.getSequenceName(); } + public boolean isAutomatic() { + return mMainProperty.isAutomatic(); + } + public boolean isVersion() { return mMainProperty.isVersion(); } @@ -1279,6 +1328,10 @@ public class JDBCStorableIntrospector extends StorableIntrospector { return mColumnName != null && !isJoin(); } + public boolean isAutoIncrement() { + return mAutoIncrement; + } + public String getColumnName() { return mColumnName; } diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableProperty.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableProperty.java index b900e69..9102c17 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableProperty.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableProperty.java @@ -45,6 +45,12 @@ public interface JDBCStorableProperty extends StorableProper */ boolean isSelectable(); + /** + * Returns true if property is declared as @Automatic and column is + * designated as auto-increment. + */ + boolean isAutoIncrement(); + /** * Returns the table column for this property. * diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java index 6034227..12d6790 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java @@ -18,20 +18,17 @@ package com.amazon.carbonado.repo.jdbc; +import java.io.IOException; +import java.lang.reflect.Method; +import java.lang.reflect.UndeclaredThrowableException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; - -import java.io.IOException; - -import java.lang.reflect.Method; -import java.lang.reflect.UndeclaredThrowableException; - import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.LinkedHashMap; import org.apache.commons.logging.LogFactory; @@ -47,7 +44,6 @@ import com.amazon.carbonado.SupportException; import com.amazon.carbonado.Transaction; import com.amazon.carbonado.Trigger; import com.amazon.carbonado.capability.IndexInfo; - import com.amazon.carbonado.filter.AndFilter; import com.amazon.carbonado.filter.Filter; import com.amazon.carbonado.filter.FilterValues; @@ -55,26 +51,22 @@ import com.amazon.carbonado.filter.OrFilter; import com.amazon.carbonado.filter.PropertyFilter; import com.amazon.carbonado.filter.RelOp; import com.amazon.carbonado.filter.Visitor; - import com.amazon.carbonado.info.ChainedProperty; import com.amazon.carbonado.info.Direction; import com.amazon.carbonado.info.OrderedProperty; import com.amazon.carbonado.info.StorableProperty; import com.amazon.carbonado.info.StorablePropertyAdapter; - -import com.amazon.carbonado.spi.SequenceValueProducer; -import com.amazon.carbonado.spi.TriggerManager; - -import com.amazon.carbonado.util.QuickConstructorGenerator; - import com.amazon.carbonado.qe.AbstractQueryExecutor; import com.amazon.carbonado.qe.OrderingList; import com.amazon.carbonado.qe.QueryExecutor; -import com.amazon.carbonado.qe.QueryExecutorFactory; import com.amazon.carbonado.qe.QueryExecutorCache; +import com.amazon.carbonado.qe.QueryExecutorFactory; import com.amazon.carbonado.qe.QueryFactory; import com.amazon.carbonado.qe.StandardQuery; import com.amazon.carbonado.qe.StandardQueryFactory; +import com.amazon.carbonado.sequence.SequenceValueProducer; +import com.amazon.carbonado.spi.TriggerManager; +import com.amazon.carbonado.util.QuickConstructorGenerator; /** * @@ -95,7 +87,7 @@ class JDBCStorage extends StandardQueryFactory final TriggerManager mTriggerManager; - JDBCStorage(JDBCRepository repository, JDBCStorableInfo info) + JDBCStorage(JDBCRepository repository, JDBCStorableInfo info, boolean autoVersioning) throws SupportException, RepositoryException { super(info.getStorableType()); @@ -103,7 +95,9 @@ class JDBCStorage extends StandardQueryFactory mSupportStrategy = repository.getSupportStrategy(); mInfo = info; - Class generatedStorableClass = JDBCStorableGenerator.getGeneratedClass(info); + Class generatedStorableClass = JDBCStorableGenerator + .getGeneratedClass(info, autoVersioning); + mInstanceFactory = QuickConstructorGenerator .getInstance(generatedStorableClass, InstanceFactory.class); @@ -134,6 +128,33 @@ class JDBCStorage extends StandardQueryFactory return property != null && property.isSupported(); } + public void truncate() throws PersistException { + String truncateFormat = mSupportStrategy.getTruncateTableStatement(); + + try { + if (truncateFormat == null || mTriggerManager.getDeleteTrigger() != null) { + query().deleteAll(); + return; + } + + Connection con = mRepository.getConnection(); + try { + java.sql.Statement st = con.createStatement(); + try { + st.execute(String.format(truncateFormat, mInfo.getQualifiedTableName())); + } finally { + st.close(); + } + } catch (SQLException e) { + throw JDBCExceptionTransformer.getInstance().toPersistException(e); + } finally { + mRepository.yieldConnection(con); + } + } catch (FetchException e) { + throw e.toPersistException(); + } + } + public boolean addTrigger(Trigger trigger) { return mTriggerManager.addTrigger(trigger); } @@ -147,7 +168,11 @@ class JDBCStorage extends StandardQueryFactory } public SequenceValueProducer getSequenceValueProducer(String name) throws PersistException { - return mSupportStrategy.getSequenceValueProducer(name); + try { + return mRepository.getSequenceValueProducer(name); + } catch (RepositoryException e) { + throw e.toPersistException(); + } } public Trigger getInsertTrigger() { @@ -172,7 +197,7 @@ class JDBCStorage extends StandardQueryFactory if (jblob != null) { try { - JDBCTransaction txn = mRepository.openTransactionManager().getTxn(); + JDBCTransaction txn = mRepository.localTxnManager().getTxn(); if (txn != null) { txn.register(jblob); } @@ -194,7 +219,7 @@ class JDBCStorage extends StandardQueryFactory if (jclob != null) { try { - JDBCTransaction txn = mRepository.openTransactionManager().getTxn(); + JDBCTransaction txn = mRepository.localTxnManager().getTxn(); if (txn != null) { txn.register(jclob); } @@ -456,13 +481,27 @@ class JDBCStorage extends StandardQueryFactory } public Cursor fetch(FilterValues values) throws FetchException { - boolean forUpdate = mRepository.openTransactionManager().isForUpdate(); + boolean forUpdate = mRepository.localTxnManager().isForUpdate(); Connection con = mRepository.getConnection(); try { - PreparedStatement ps = con.prepareStatement(prepareSelect(values, forUpdate)); + boolean scrollInsensitiveReadOnly = + mRepository.supportsScrollInsensitiveReadOnly(); + + PreparedStatement ps; + + if (scrollInsensitiveReadOnly) { + // Can support fast skipping. + ps = con.prepareStatement + (prepareSelect(values, forUpdate), + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); + } else { + // Can't support fast skipping. + ps = con.prepareStatement(prepareSelect(values, forUpdate)); + } + try { setParameters(ps, values); - return new JDBCCursor(JDBCStorage.this, con, ps); + return new JDBCCursor(JDBCStorage.this, con, ps, scrollInsensitiveReadOnly); } catch (Exception e) { // in case of exception, close statement try { @@ -519,7 +558,7 @@ class JDBCStorage extends StandardQueryFactory throws IOException { indent(app, indentLevel); - boolean forUpdate = mRepository.openTransactionManager().isForUpdate(); + boolean forUpdate = mRepository.localTxnManager().isForUpdate(); app.append(prepareSelect(values, forUpdate)); app.append('\n'); return true; @@ -529,7 +568,7 @@ class JDBCStorage extends StandardQueryFactory throws IOException { try { - boolean forUpdate = mRepository.openTransactionManager().isForUpdate(); + boolean forUpdate = mRepository.localTxnManager().isForUpdate(); String statement = prepareSelect(values, forUpdate); return mRepository.getSupportStrategy().printPlan(app, indentLevel, statement); } catch (FetchException e) { diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCSupportStrategy.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCSupportStrategy.java index f9ca221..8a27c3d 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCSupportStrategy.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCSupportStrategy.java @@ -18,8 +18,8 @@ package com.amazon.carbonado.repo.jdbc; -import java.io.InputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.io.Writer; @@ -27,15 +27,16 @@ import java.io.Writer; import java.sql.PreparedStatement; import java.sql.SQLException; -import java.util.HashMap; -import java.util.Map; - import com.amazon.carbonado.FetchException; import com.amazon.carbonado.PersistException; +import com.amazon.carbonado.RepositoryException; +import com.amazon.carbonado.UnsupportedTypeException; -import com.amazon.carbonado.util.ThrowUnchecked; +import com.amazon.carbonado.sequence.SequenceValueGenerator; +import com.amazon.carbonado.sequence.SequenceValueProducer; +import com.amazon.carbonado.sequence.StoredSequence; -import com.amazon.carbonado.spi.SequenceValueProducer; +import com.amazon.carbonado.util.ThrowUnchecked; /** * Allows database product specific features to be abstracted. @@ -77,11 +78,12 @@ class JDBCSupportStrategy { return new JDBCSupportStrategy(repo); } - + protected final JDBCRepository mRepo; - - private Map mSequences; - + private String mSequenceSelectStatement; + private boolean mForceStoredSequence = false; + private String mTruncateTableStatement; + protected JDBCSupportStrategy(JDBCRepository repo) { mRepo = repo; } @@ -89,38 +91,30 @@ class JDBCSupportStrategy { JDBCExceptionTransformer createExceptionTransformer() { return new JDBCExceptionTransformer(); } - - /** - * Utility method used by generated storables to get sequence values during - * an insert operation. - * - * @param sequenceName name of sequence - * @throws PersistException instead of FetchException since this code is - * called during an insert operation - */ - synchronized SequenceValueProducer getSequenceValueProducer(String sequenceName) - throws PersistException + + SequenceValueProducer createSequenceValueProducer(String name) + throws RepositoryException { - SequenceValueProducer sequence = mSequences == null ? null : mSequences.get(sequenceName); - - if (sequence == null) { - String sequenceQuery = createSequenceQuery(sequenceName); - sequence = new JDBCSequenceValueProducer(mRepo, sequenceQuery); - if (mSequences == null) { - mSequences = new HashMap(); + if (name == null) { + throw new IllegalArgumentException("Sequence name is null"); + } + String format = getSequenceSelectStatement(); + if (format != null && format.length() > 0 && !isForceStoredSequence()) { + String sequenceQuery = String.format(format, name); + return new JDBCSequenceValueProducer(mRepo, sequenceQuery); + } else { + try { + return new SequenceValueGenerator(mRepo, name); + } catch (UnsupportedTypeException e) { + if (e.getType() != StoredSequence.class) { + throw e; + } + throw new PersistException + ("Native sequences are not currently supported for \"" + + mRepo.getDatabaseProductName() + "\". Instead, define a table named " + + "CARBONADO_SEQUENCE as required by " + StoredSequence.class.getName() + '.'); } - mSequences.put(sequenceName, sequence); } - - return sequence; - } - - String createSequenceQuery(String sequenceName) { - throw new UnsupportedOperationException - ("Sequences are not supported by default JDBC support strategy. " + - "If \"" + mRepo.getDatabaseProductName() + "\" actually does support sequences, " + - "then a custom support strategy might be available in a separate jar. " + - "If so, simply add it to your classpath."); } /** @@ -240,4 +234,39 @@ class JDBCSupportStrategy { { return false; } + + /** + * Returns the optional sequence select statement format. The format is + * printf style with 1 string parameter which can be passed through {@link + * String#format(String, Object[])} to create a sql statement. + * @return + */ + String getSequenceSelectStatement() { + return mSequenceSelectStatement; + } + + void setSequenceSelectStatement(String sequenceSelectStatement) { + mSequenceSelectStatement = sequenceSelectStatement; + } + + boolean isForceStoredSequence() { + return mForceStoredSequence; + } + + void setForceStoredSequence(boolean forceStoredSequence) { + mForceStoredSequence = forceStoredSequence; + } + + /** + * Return the optional truncate table statement format. The format is + * printf style with 1 string parameter which can be passed through {@link + * String#format(String, Object[])} to create a sql statement. + */ + String getTruncateTableStatement() { + return mTruncateTableStatement; + } + + void setTruncateTableStatement(String truncateTableStatement) { + mTruncateTableStatement = truncateTableStatement; + } } diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingCallableStatement.java b/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingCallableStatement.java index 3e5156b..140fbd0 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingCallableStatement.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingCallableStatement.java @@ -386,7 +386,6 @@ class LoggingCallableStatement extends LoggingPreparedStatement implements Calla return cs().getURL(parameterName); } - /* JDK 1.6 features public RowId getRowId(int parameterIndex) throws SQLException { return cs().getRowId(parameterIndex); } @@ -542,7 +541,6 @@ class LoggingCallableStatement extends LoggingPreparedStatement implements Calla { cs().setNClob(parameterName, reader); } - */ private CallableStatement cs() { return (CallableStatement) mStatement; diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingConnection.java b/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingConnection.java index 47d734e..3a2a6cb 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingConnection.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingConnection.java @@ -225,7 +225,6 @@ class LoggingConnection implements Connection { mCon.releaseSavepoint(savepoint); } - /* JDK 1.6 features public Clob createClob() throws SQLException { return mCon.createClob(); } @@ -277,5 +276,4 @@ class LoggingConnection implements Connection { public boolean isWrapperFor(Class iface) throws SQLException { throw new UnsupportedOperationException(); } - */ } diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingDataSource.java b/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingDataSource.java index 74c120e..ab6a2f9 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingDataSource.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingDataSource.java @@ -91,7 +91,14 @@ public class LoggingDataSource implements DataSource { return mDataSource.getLoginTimeout(); } - /* JDK 1.6 features + public void close() throws SQLException { + mLog.debug("DataSource.close()"); + if (!JDBCRepository.closeDataSource(mDataSource)) { + mLog.debug("DataSource doesn't have a close method: " + + mDataSource.getClass().getName()); + } + } + public T unwrap(Class iface) throws SQLException { throw new UnsupportedOperationException(); } @@ -99,5 +106,4 @@ public class LoggingDataSource implements DataSource { public boolean isWrapperFor(Class iface) throws SQLException { throw new UnsupportedOperationException(); } - */ } diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingPreparedStatement.java b/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingPreparedStatement.java index f5b355f..6fbfa9b 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingPreparedStatement.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingPreparedStatement.java @@ -249,7 +249,6 @@ class LoggingPreparedStatement extends LoggingStatement implements PreparedState return ps().getParameterMetaData(); } - /* JDK 1.6 features public void setRowId(int parameterIndex, RowId x) throws SQLException { ps().setRowId(parameterIndex, x); } @@ -339,7 +338,6 @@ class LoggingPreparedStatement extends LoggingStatement implements PreparedState public void setNClob(int parameterIndex, java.io.Reader reader) throws SQLException { ps().setNClob(parameterIndex, reader); } - */ private PreparedStatement ps() { return (PreparedStatement) mStatement; diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingStatement.java b/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingStatement.java index c6b1f82..5dc8d5e 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingStatement.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingStatement.java @@ -198,7 +198,6 @@ class LoggingStatement implements Statement { return mStatement.getResultSetHoldability(); } - /* JDK 1.6 features public boolean isClosed() throws SQLException { return mStatement.isClosed(); } @@ -218,5 +217,4 @@ class LoggingStatement implements Statement { public boolean isWrapperFor(Class iface) throws SQLException { throw new UnsupportedOperationException(); } - */ } diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/MysqlSupportStrategy.java b/src/main/java/com/amazon/carbonado/repo/jdbc/MysqlSupportStrategy.java index d9b5579..27f813f 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/MysqlSupportStrategy.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/MysqlSupportStrategy.java @@ -24,8 +24,12 @@ package com.amazon.carbonado.repo.jdbc; * @author Brian S O'Neill */ class MysqlSupportStrategy extends JDBCSupportStrategy { + private static final String TRUNCATE_STATEMENT = "TRUNCATE TABLE %s"; + protected MysqlSupportStrategy(JDBCRepository repo) { super(repo); + + setTruncateTableStatement(TRUNCATE_STATEMENT); } JDBCExceptionTransformer createExceptionTransformer() { diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/OracleSupportStrategy.java b/src/main/java/com/amazon/carbonado/repo/jdbc/OracleSupportStrategy.java index 3efa40d..356d8d2 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/OracleSupportStrategy.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/OracleSupportStrategy.java @@ -18,29 +18,28 @@ package com.amazon.carbonado.repo.jdbc; -import java.io.IOException; - import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; - -import java.sql.Connection; import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.Statement; import java.sql.SQLException; import org.apache.commons.logging.LogFactory; import com.amazon.carbonado.FetchException; import com.amazon.carbonado.PersistException; -import com.amazon.carbonado.Transaction; /** * * * @author Brian S O'Neill + * @author bcastill */ class OracleSupportStrategy extends JDBCSupportStrategy { + + private static final String DEFAULT_SEQUENCE_SELECT_STATEMENT = "SELECT %s.NEXTVAL FROM DUAL"; + + private static final String TRUNCATE_STATEMENT = "TRUNCATE TABLE %s"; + private static final int LOB_CHUNK_LIMIT = 4000; private static final String PLAN_TABLE_NAME = "TEMP_CARBONADO_PLAN_TABLE"; @@ -60,6 +59,11 @@ class OracleSupportStrategy extends JDBCSupportStrategy { protected OracleSupportStrategy(JDBCRepository repo) { super(repo); + // Set printf style format to create sequence query + setSequenceSelectStatement(DEFAULT_SEQUENCE_SELECT_STATEMENT); + + setTruncateTableStatement(TRUNCATE_STATEMENT); + // Access all the custom oracle.sql.BLOB methods via reflection. { Method blob_empty_lob = null; @@ -126,13 +130,6 @@ class OracleSupportStrategy extends JDBCSupportStrategy { return new OracleExceptionTransformer(); } - @Override - String createSequenceQuery(String sequenceName) { - return new StringBuilder(25 + sequenceName.length()) - .append("SELECT ").append(sequenceName).append(".NEXTVAL FROM DUAL") - .toString(); - } - @Override JDBCBlob convertBlob(java.sql.Blob blob, JDBCBlobLoader loader) { return blob == null ? null : new OracleBlob(mRepo, blob, loader); diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/SimpleDataSource.java b/src/main/java/com/amazon/carbonado/repo/jdbc/SimpleDataSource.java index 431c820..ee355de 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/SimpleDataSource.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/SimpleDataSource.java @@ -87,7 +87,9 @@ public class SimpleDataSource implements DataSource { return 0; } - /* JDK 1.6 features + public void close() throws SQLException { + } + public T unwrap(Class iface) throws SQLException { throw new UnsupportedOperationException(); } @@ -95,5 +97,4 @@ public class SimpleDataSource implements DataSource { public boolean isWrapperFor(Class iface) throws SQLException { throw new UnsupportedOperationException(); } - */ } diff --git a/src/main/java/com/amazon/carbonado/repo/logging/LoggingRepository.java b/src/main/java/com/amazon/carbonado/repo/logging/LoggingRepository.java index 611df4f..89e6e21 100644 --- a/src/main/java/com/amazon/carbonado/repo/logging/LoggingRepository.java +++ b/src/main/java/com/amazon/carbonado/repo/logging/LoggingRepository.java @@ -31,7 +31,7 @@ import com.amazon.carbonado.TriggerFactory; import com.amazon.carbonado.capability.Capability; -import com.amazon.carbonado.spi.StorageCollection; +import com.amazon.carbonado.spi.StoragePool; /** * @@ -44,7 +44,7 @@ class LoggingRepository implements Repository, LogAccessCapability { private final Repository mRepo; private final Log mLog; - private final StorageCollection mStorages; + private final StoragePool mStoragePool; LoggingRepository(AtomicReference rootRef, Iterable triggerFactories, @@ -55,7 +55,7 @@ class LoggingRepository implements Repository, LogAccessCapability { mRepo = actual; mLog = log; - mStorages = new StorageCollection() { + mStoragePool = new StoragePool() { protected Storage createStorage(Class type) throws RepositoryException { @@ -71,7 +71,7 @@ class LoggingRepository implements Repository, LogAccessCapability { public Storage storageFor(Class type) throws SupportException, RepositoryException { - return mStorages.storageFor(type); + return mStoragePool.get(type); } public Transaction enterTransaction() { diff --git a/src/main/java/com/amazon/carbonado/repo/logging/LoggingStorage.java b/src/main/java/com/amazon/carbonado/repo/logging/LoggingStorage.java index 3f807c2..de1118f 100644 --- a/src/main/java/com/amazon/carbonado/repo/logging/LoggingStorage.java +++ b/src/main/java/com/amazon/carbonado/repo/logging/LoggingStorage.java @@ -41,6 +41,14 @@ class LoggingStorage extends WrappedStorage { mRepo = repo; } + public void truncate() throws PersistException { + Log log = mRepo.getLog(); + if (log.isEnabled()) { + log.write("Storage.truncate() on " + getStorableType().getClass()); + } + super.truncate(); + } + protected S wrap(S storable) { return super.wrap(storable); } diff --git a/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedRepository.java b/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedRepository.java index 3b215bc..2c73200 100644 --- a/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedRepository.java +++ b/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedRepository.java @@ -53,7 +53,7 @@ import com.amazon.carbonado.info.Direction; import com.amazon.carbonado.info.StorableInfo; import com.amazon.carbonado.info.StorableIntrospector; -import com.amazon.carbonado.spi.StorageCollection; +import com.amazon.carbonado.spi.StoragePool; import com.amazon.carbonado.spi.TransactionPair; import com.amazon.carbonado.util.Throttle; @@ -140,7 +140,7 @@ class ReplicatedRepository private Repository mReplicaRepository; private Repository mMasterRepository; - private final StorageCollection mStorages; + private final StoragePool mStoragePool; ReplicatedRepository(String aName, Repository aReplicaRepository, @@ -149,7 +149,7 @@ class ReplicatedRepository mReplicaRepository = aReplicaRepository; mMasterRepository = aMasterRepository; - mStorages = new StorageCollection() { + mStoragePool = new StoragePool() { protected Storage createStorage(Class type) throws SupportException, RepositoryException { @@ -199,7 +199,7 @@ class ReplicatedRepository public Storage storageFor(Class type) throws MalformedTypeException, SupportException, RepositoryException { - return mStorages.storageFor(type); + return mStoragePool.get(type); } public Transaction enterTransaction() { diff --git a/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedStorage.java b/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedStorage.java index 9711784..5d3f47b 100644 --- a/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedStorage.java +++ b/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedStorage.java @@ -21,6 +21,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.amazon.carbonado.FetchException; +import com.amazon.carbonado.PersistException; import com.amazon.carbonado.Query; import com.amazon.carbonado.Storage; import com.amazon.carbonado.Storable; @@ -105,6 +106,11 @@ class ReplicatedStorage implements Storage { return mReplicaStorage.query(filter); } + public void truncate() throws PersistException { + mMasterStorage.truncate(); + mReplicaStorage.truncate(); + } + public boolean addTrigger(Trigger trigger) { return mReplicationTrigger.addTrigger(trigger); } diff --git a/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBCursor.java b/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBCursor.java index 5d21dad..f16d2f7 100644 --- a/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBCursor.java +++ b/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBCursor.java @@ -25,6 +25,8 @@ import com.amazon.carbonado.Storable; import com.amazon.carbonado.raw.RawCursor; import com.amazon.carbonado.raw.RawUtil; +import com.amazon.carbonado.spi.TransactionManager; + /** * * @@ -33,7 +35,7 @@ import com.amazon.carbonado.raw.RawUtil; abstract class BDBCursor extends RawCursor { private static final byte[] NO_DATA = new byte[0]; - private final BDBTransactionManager mTxnMgr; + private final TransactionManager mTxnMgr; private final BDBStorage mStorage; /** * @param txnMgr @@ -48,7 +50,7 @@ abstract class BDBCursor extends RawCursor { * @throws ClassCastException if lock is not an object passed by * {@link BDBStorage#openCursor BDBStorage.openCursor} */ - protected BDBCursor(BDBTransactionManager txnMgr, + protected BDBCursor(TransactionManager txnMgr, byte[] startBound, boolean inclusiveStart, byte[] endBound, boolean inclusiveEnd, int maxPrefix, diff --git a/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBRepository.java b/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBRepository.java index 6fc7cdd..0e2ed96 100644 --- a/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBRepository.java +++ b/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBRepository.java @@ -22,23 +22,15 @@ import java.io.File; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cojen.util.WeakIdentityMap; - import com.amazon.carbonado.ConfigurationException; import com.amazon.carbonado.Cursor; import com.amazon.carbonado.FetchException; import com.amazon.carbonado.IsolationLevel; -import com.amazon.carbonado.MalformedTypeException; import com.amazon.carbonado.PersistException; import com.amazon.carbonado.Repository; import com.amazon.carbonado.RepositoryException; @@ -63,11 +55,14 @@ import com.amazon.carbonado.qe.StorageAccess; import com.amazon.carbonado.raw.StorableCodecFactory; +import com.amazon.carbonado.sequence.SequenceCapability; +import com.amazon.carbonado.sequence.SequenceValueGenerator; +import com.amazon.carbonado.sequence.SequenceValueProducer; + +import com.amazon.carbonado.spi.AbstractRepository; import com.amazon.carbonado.spi.ExceptionTransformer; import com.amazon.carbonado.spi.LobEngine; -import com.amazon.carbonado.spi.SequenceValueGenerator; -import com.amazon.carbonado.spi.SequenceValueProducer; -import com.amazon.carbonado.spi.StorageCollection; +import com.amazon.carbonado.spi.TransactionManager; /** * Repository implementation backed by a Berkeley DB. Data is encoded in the @@ -78,42 +73,31 @@ import com.amazon.carbonado.spi.StorageCollection; * @author Brian S O'Neill * @author Vidya Iyer * @author Nicole Deflaux + * @author bcastill */ -abstract class BDBRepository +abstract class BDBRepository extends AbstractRepository implements Repository, RepositoryAccess, IndexInfoCapability, CheckpointCapability, EnvironmentCapability, ShutdownCapability, - StorableInfoCapability + StorableInfoCapability, + SequenceCapability { private final Log mLog = LogFactory.getLog(getClass()); - private final String mName; private final boolean mIsMaster; final Iterable mTriggerFactories; private final AtomicReference mRootRef; private final StorableCodecFactory mStorableCodecFactory; private final ExceptionTransformer mExTransformer; - private final StorageCollection mStorages; - private final Map mSequences; - private final ThreadLocal> mCurrentTxnMgr; - - private final Lock mShutdownLock; - private final Condition mShutdownCondition; - private int mShutdownBlockerCount; - - // Weakly tracks all BDBTransactionManager instances for shutdown hook. - private final Map, ?> mAllTxnMgrs; Checkpointer mCheckpointer; DeadlockDetector mDeadlockDetector; - private ShutdownHook mShutdownHook; - final Runnable mPreShutdownHook; - final Runnable mPostShutdownHook; - volatile boolean mHasShutdown; + private final Runnable mPreShutdownHook; + private final Runnable mPostShutdownHook; private final Object mInitialDBConfig; private final BDBRepositoryBuilder.DatabaseHook mDatabaseHook; @@ -126,8 +110,6 @@ abstract class BDBRepository final File mEnvHome; final String mSingleFileName; - private final String mMergeSortTempDir; - private LayoutFactory mLayoutFactory; private LobEngine mLobEngine; @@ -146,40 +128,19 @@ abstract class BDBRepository ExceptionTransformer exTransformer) throws ConfigurationException { + super(builder.getName()); + builder.assertReady(); if (exTransformer == null) { throw new IllegalArgumentException("Exception transformer must not be null"); } - mName = builder.getName(); mIsMaster = builder.isMaster(); mTriggerFactories = builder.getTriggerFactories(); mRootRef = rootRef; mExTransformer = exTransformer; - mStorages = new StorageCollection() { - protected Storage createStorage(Class type) - throws RepositoryException - { - lockoutShutdown(); - try { - try { - return BDBRepository.this.createStorage(type); - } catch (Exception e) { - throw toRepositoryException(e); - } - } finally { - unlockoutShutdown(); - } - } - }; - - mSequences = new ConcurrentHashMap(); - mCurrentTxnMgr = new ThreadLocal>(); - mShutdownLock = new ReentrantLock(); - mShutdownCondition = mShutdownLock.newCondition(); - mAllTxnMgrs = new WeakIdentityMap(); mRunCheckpointer = !builder.getReadOnly() && builder.getRunCheckpointer(); mRunDeadlockDetector = builder.getRunDeadlockDetector(); mStorableCodecFactory = builder.getStorableCodecFactory(); @@ -191,40 +152,13 @@ abstract class BDBRepository mDataHome = builder.getDataHomeFile(); mEnvHome = builder.getEnvironmentHomeFile(); mSingleFileName = builder.getSingleFileName(); - // FIXME: see comments in builder - mMergeSortTempDir = null; //builder.getMergeSortTempDirectory(); - } - - public String getName() { - return mName; - } - - public BDBStorage storageFor(Class type) - throws MalformedTypeException, RepositoryException - { - return (BDBStorage) mStorages.storageFor(type); - } - - public Transaction enterTransaction() { - return openTransactionManager().enter(null); - } - - public Transaction enterTransaction(IsolationLevel level) { - return openTransactionManager().enter(level); - } - - public Transaction enterTopTransaction(IsolationLevel level) { - return openTransactionManager().enterTop(level); - } - - public IsolationLevel getTransactionIsolationLevel() { - return openTransactionManager().getIsolationLevel(); } @SuppressWarnings("unchecked") public C getCapability(Class capabilityType) { - if (capabilityType.isInstance(this)) { - return (C) this; + C cap = super.getCapability(capabilityType); + if (cap != null) { + return cap; } if (capabilityType == LayoutCapability.class) { return (C) mLayoutFactory; @@ -276,65 +210,6 @@ abstract class BDBRepository return StorableIntrospector.examine(type).getAllProperties().get(name) != null; } - public void close() { - shutdown(false); - } - - public boolean isAutoShutdownEnabled() { - return mShutdownHook != null; - } - - 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; - } - } - } - - 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; - } - } - /** * Suspend the checkpointer until the suspension time has expired or until * manually resumed. If a checkpoint is in progress, this method will block @@ -390,7 +265,7 @@ abstract class BDBRepository public StorageAccess storageAccessFor(Class type) throws RepositoryException { - return storageFor(type); + return (BDBStorage) storageFor(type); } @Override @@ -398,6 +273,78 @@ abstract class BDBRepository close(); } + protected void shutdownHook() { + // Run any external shutdown logic that needs to happen before the + // databases and the environment are actually closed + if (mPreShutdownHook != null) { + mPreShutdownHook.run(); + } + + // Close database handles. + for (Storage storage : allStorage()) { + try { + if (storage instanceof BDBStorage) { + ((BDBStorage) storage).close(); + } + } catch (Throwable e) { + getLog().error(null, e); + } + } + + // Wait for checkpointer to finish. + if (mCheckpointer != null) { + mCheckpointer.interrupt(); + try { + mCheckpointer.join(); + } catch (InterruptedException e) { + } + } + + // Wait for deadlock detector to finish. + if (mDeadlockDetector != null) { + mDeadlockDetector.interrupt(); + try { + mDeadlockDetector.join(); + } catch (InterruptedException e) { + } + } + + // Close environment. + try { + env_close(); + } catch (Throwable e) { + getLog().error(null, e); + } + + if (mPostShutdownHook != null) { + mPostShutdownHook.run(); + } + } + + protected Log getLog() { + return mLog; + } + + protected TransactionManager createTransactionManager() { + return new BDBTransactionManager(mExTransformer, this); + } + + protected Storage createStorage(Class type) + throws RepositoryException + { + try { + return createBDBStorage(type); + } catch (Exception e) { + throw toRepositoryException(e); + } + } + + protected SequenceValueProducer createSequenceValueProducer(String name) + throws RepositoryException + { + return new SequenceValueGenerator(BDBRepository.this, name); + } + /** * @see com.amazon.carbonado.spi.RepositoryBuilder#isMaster */ @@ -417,40 +364,6 @@ abstract class BDBRepository return dbName; } - String getMergeSortTempDirectory() { - if (mMergeSortTempDir != null) { - new File(mMergeSortTempDir).mkdirs(); - } - return mMergeSortTempDir; - } - - SequenceValueProducer getSequenceValueProducer(String name) throws PersistException { - SequenceValueGenerator producer = mSequences.get(name); - if (producer == null) { - lockoutShutdown(); - try { - producer = mSequences.get(name); - if (producer == null) { - Repository metaRepo = getRootRepository(); - try { - producer = new SequenceValueGenerator(metaRepo, name); - } catch (RepositoryException e) { - throw toPersistException(e); - } - mSequences.put(name, producer); - } - return producer; - } finally { - unlockoutShutdown(); - } - } - return producer; - } - - Log getLog() { - return mLog; - } - StorableCodecFactory getStorableCodecFactory() { return mStorableCodecFactory; } @@ -464,7 +377,7 @@ abstract class BDBRepository LobEngine getLobEngine() throws RepositoryException { if (mLobEngine == null) { - mLobEngine = new LobEngine(getRootRepository()); + mLobEngine = new LobEngine(this, getRootRepository()); } return mLobEngine; } @@ -555,7 +468,7 @@ abstract class BDBRepository */ abstract void env_close() throws Exception; - abstract BDBStorage createStorage(Class type) + abstract BDBStorage createBDBStorage(Class type) throws Exception; FetchException toFetchException(Throwable e) { @@ -571,70 +484,11 @@ abstract class BDBRepository } /** - * Returns the thread-local BDBTransactionManager instance, creating it if - * needed. - */ - BDBTransactionManager openTransactionManager() { - BDBTransactionManager txnMgr = mCurrentTxnMgr.get(); - if (txnMgr == null) { - lockoutShutdown(); - try { - txnMgr = new BDBTransactionManager(mExTransformer, this); - mCurrentTxnMgr.set(txnMgr); - mAllTxnMgrs.put(txnMgr, null); - } finally { - unlockoutShutdown(); - } - } - return txnMgr; - } - - /** - * Call to prevent shutdown hook from running. Be sure to call - * unlockoutShutdown afterwards. - */ - private void lockoutShutdown() { - mShutdownLock.lock(); - try { - mShutdownBlockerCount++; - } finally { - mShutdownLock.unlock(); - } - } - - /** - * Only call this to release lockoutShutdown. + * Returns the thread-local BDBTransactionManager, creating it if needed. */ - private void unlockoutShutdown() { - mShutdownLock.lock(); - try { - if (--mShutdownBlockerCount == 0) { - mShutdownCondition.signalAll(); - } - } finally { - mShutdownLock.unlock(); - } - } - - /** - * Only to be called by shutdown hook itself. - */ - void lockForShutdown() { - mShutdownLock.lock(); - while (mShutdownBlockerCount > 0) { - try { - mShutdownCondition.await(); - } catch (InterruptedException e) { - mLog.warn("Ignoring interruption for shutdown"); - } - } - } - - /** - * Only to be called by shutdown hook itself. - */ - void unlockForShutdown() { - mShutdownLock.unlock(); + // Provides access to transaction manager from other classes. + TransactionManager localTxnManager() { + return localTransactionManager(); } /** @@ -667,7 +521,8 @@ abstract class BDBRepository * since last checkpoint */ Checkpointer(BDBRepository repository, long sleepInterval, int kBytes, int minutes) { - super("BDBRepository checkpointer (" + repository.getName() + ')'); + super(repository.getClass().getSimpleName() + " checkpointer (" + + repository.getName() + ')'); setDaemon(true); mRepository = new WeakReference(repository); mSleepInterval = sleepInterval; @@ -800,7 +655,8 @@ abstract class BDBRepository * @param sleepInterval milliseconds to sleep before running deadlock detection */ DeadlockDetector(BDBRepository repository, long sleepInterval) { - super("BDBRepository deadlock detector (" + repository.getName() + ')'); + super(repository.getClass().getSimpleName() + " deadlock detector (" + + repository.getName() + ')'); setDaemon(true); mRepository = new WeakReference(repository); mSleepInterval = sleepInterval; @@ -831,111 +687,4 @@ abstract class BDBRepository } } } - - private static class ShutdownHook extends Thread { - private final WeakReference> mRepository; - - ShutdownHook(BDBRepository repository) { - super("BDBRepository shutdown (" + repository.getName() + ')'); - mRepository = new WeakReference>(repository); - } - - public void run() { - run(true); - } - - public void run(boolean suspendThreads) { - BDBRepository repository = mRepository.get(); - if (repository == null) { - return; - } - - repository.getLog().info("Closing repository \"" + repository.getName() + '"'); - - try { - doShutdown(repository, suspendThreads); - } finally { - repository.mHasShutdown = true; - mRepository.clear(); - repository.getLog().info - ("Finished closing repository \"" + repository.getName() + '"'); - } - } - - private void doShutdown(BDBRepository repository, boolean suspendThreads) { - repository.lockForShutdown(); - try { - // Return unused sequence values. - for (SequenceValueGenerator generator : repository.mSequences.values()) { - try { - generator.returnReservedValues(); - } catch (RepositoryException e) { - repository.getLog().warn(null, e); - } - } - - // Close transactions and cursors. - for (BDBTransactionManager 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.getLog().error(null, e); - } - } - - // Run any external shutdown logic that needs to - // happen before the databases and the environment are - // actually closed - if (repository.mPreShutdownHook != null) { - repository.mPreShutdownHook.run(); - } - - // Close database handles. - for (Storage storage : repository.mStorages.allStorage()) { - try { - ((BDBStorage) storage).close(); - } catch (Throwable e) { - repository.getLog().error(null, e); - } - } - - // Wait for checkpointer to finish. - if (repository.mCheckpointer != null) { - repository.mCheckpointer.interrupt(); - try { - repository.mCheckpointer.join(); - } catch (InterruptedException e) { - } - } - - // Wait for deadlock detector to finish. - if (repository.mDeadlockDetector != null) { - repository.mDeadlockDetector.interrupt(); - try { - repository.mDeadlockDetector.join(); - } catch (InterruptedException e) { - } - } - - // Close environment. - try { - repository.env_close(); - } catch (Throwable e) { - repository.getLog().error(null, e); - } - - if (repository.mPostShutdownHook != null) { - repository.mPostShutdownHook.run(); - } - } finally { - repository.unlockForShutdown(); - } - } - } } diff --git a/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBRepositoryBuilder.java b/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBRepositoryBuilder.java index 1f60bfd..fa24624 100644 --- a/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBRepositoryBuilder.java +++ b/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBRepositoryBuilder.java @@ -64,6 +64,7 @@ import com.amazon.carbonado.ConfigurationException; *
  • {@link com.amazon.carbonado.capability.StorableInfoCapability StorableInfoCapability} *
  • {@link com.amazon.carbonado.capability.ShutdownCapability ShutdownCapability} *
  • {@link com.amazon.carbonado.layout.LayoutCapability LayoutCapability} + *
  • {@link com.amazon.carbonado.sequence.SequenceCapability SequenceCapability} *
  • {@link CheckpointCapability CheckpointCapability} *
  • {@link EnvironmentCapability EnvironmentCapability} * @@ -82,7 +83,6 @@ public class BDBRepositoryBuilder extends AbstractRepositoryBuilder { private boolean mIsMaster = true; private BDBProduct mProduct = DEFAULT_PRODUCT; private File mEnvHome; - private String mMergeSortTempDir; private File mDataHome; private String mSingleFileName; private boolean mIndexSupport = true; @@ -282,29 +282,6 @@ public class BDBRepositoryBuilder extends AbstractRepositoryBuilder { return getDataHomeFile().getPath(); } - /** - * Sets the directory to use for creating temporary files needed for merge - * sorting. If null or not specified, the default temporary file directory is used. - * - * @param tempDir directory to store temp files for merge sorting, or null - * for default - */ - /* FIXME: use common config somehow, since indexed repo needs this too - public void setMergeSortTempDirectory(String tempDir) { - mMergeSortTempDir = tempDir; - } - */ - - /** - * Returns the directory to use for creating temporary files needed for - * merge sorting. If null, the default temporary file directory is used. - */ - /* FIXME: use common config somehow, since indexed repo needs this too - public String getMergeSortTempDirectory() { - return mMergeSortTempDir; - } - */ - /** * Specify that all BDB databases should reside in one file, except for log * files and caches. The filename is relative to the environment home, diff --git a/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBStorage.java b/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBStorage.java index d8a3066..82ba789 100644 --- a/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBStorage.java +++ b/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBStorage.java @@ -71,10 +71,12 @@ import com.amazon.carbonado.raw.StorableCodecFactory; import com.amazon.carbonado.raw.RawSupport; import com.amazon.carbonado.raw.RawUtil; +import com.amazon.carbonado.sequence.SequenceValueProducer; + import com.amazon.carbonado.spi.IndexInfoImpl; import com.amazon.carbonado.spi.LobEngine; -import com.amazon.carbonado.spi.SequenceValueProducer; import com.amazon.carbonado.spi.StorableIndexSet; +import com.amazon.carbonado.spi.TransactionManager; import com.amazon.carbonado.spi.TriggerManager; /** @@ -154,7 +156,7 @@ abstract class BDBStorage implements Storage, Storag } public S prepare() { - return mStorableCodec.instantiate(mRawSupport); + return mStorableCodec.instantiate(); } public Query query() throws FetchException { @@ -169,6 +171,48 @@ abstract class BDBStorage implements Storage, Storag return mQueryEngine.query(filter); } + public void truncate() throws PersistException { + if (mTriggerManager.getDeleteTrigger() != null || mRepository.mSingleFileName != null) { + final int batchSize = 100; + + while (true) { + Transaction txn = mRepository.enterTransaction(IsolationLevel.READ_COMMITTED); + txn.setForUpdate(true); + try { + Cursor cursor = query().fetch(); + if (!cursor.hasNext()) { + break; + } + int count = 0; + do { + cursor.next().tryDelete(); + } while (count++ < batchSize && cursor.hasNext()); + txn.commit(); + } catch (FetchException e) { + throw e.toPersistException(); + } finally { + txn.exit(); + } + + return; + } + } + + TransactionManager txnMgr = localTxnManager(); + + // Lock out shutdown task. + txnMgr.getLock().lock(); + try { + try { + db_truncate(txnMgr.getTxn()); + } catch (Exception e) { + throw toPersistException(e); + } + } finally { + txnMgr.getLock().unlock(); + } + } + public boolean addTrigger(Trigger trigger) { return mTriggerManager.addTrigger(trigger); } @@ -222,9 +266,6 @@ abstract class BDBStorage implements Storage, Storag } } - // FIXME: sort buffer should be on repository access. Also, create abstract - // repository access that creates the correct merge sort buffer. And more: - // create capability for managing merge sort buffers. return new MergeSortBuffer(mRootStorage); } @@ -262,7 +303,7 @@ abstract class BDBStorage implements Storage, Storag boolean reverseOrder) throws FetchException { - BDBTransactionManager txnMgr = openTransactionManager(); + TransactionManager txnMgr = localTxnManager(); if (reverseRange) { { @@ -367,6 +408,12 @@ abstract class BDBStorage implements Storage, Storag * differences between the current index set and the desired index set. */ protected void open(boolean readOnly) throws RepositoryException { + open(readOnly, null, true); + } + + protected void open(boolean readOnly, Txn openTxn, boolean installTriggers) + throws RepositoryException + { final Layout layout = getLayout(); StorableInfo info = StorableIntrospector.examine(getStorableType()); @@ -386,11 +433,11 @@ abstract class BDBStorage implements Storage, Storag boolean isPrimaryEmpty; try { - BDBTransactionManager txnMgr = mRepository.openTransactionManager(); + TransactionManager txnMgr = mRepository.localTxnManager(); // Lock out shutdown task. txnMgr.getLock().lock(); try { - primaryDatabase = env_openPrimaryDatabase(null, databaseName); + primaryDatabase = env_openPrimaryDatabase(openTxn, databaseName); primaryInfo = registerPrimaryDatabase(readOnly, layout); isPrimaryEmpty = db_isEmpty(null, primaryDatabase, txnMgr.isForUpdate()); } finally { @@ -446,7 +493,8 @@ abstract class BDBStorage implements Storage, Storag try { mStorableCodec = codecFactory - .createCodec(getStorableType(), pkIndex, mRepository.isMaster(), layout); + .createCodec(getStorableType(), pkIndex, mRepository.isMaster(), layout, + mRawSupport); } catch (SupportException e) { // We've opened the database prematurely, since type isn't // supported by encoding strategy. Close it down and unregister. @@ -468,12 +516,14 @@ abstract class BDBStorage implements Storage, Storag mQueryEngine = new QueryEngine(getStorableType(), mRepository); - // Don't install automatic triggers until we're completely ready. - mTriggerManager.addTriggers(getStorableType(), mRepository.mTriggerFactories); + if (installTriggers) { + // Don't install automatic triggers until we're completely ready. + mTriggerManager.addTriggers(getStorableType(), mRepository.mTriggerFactories); + } } protected S instantiate(byte[] key, byte[] value) throws FetchException { - return mStorableCodec.instantiate(mRawSupport, key, value); + return mStorableCodec.instantiate(key, value); } protected CompactionCapability.Result compact() throws RepositoryException { @@ -493,7 +543,7 @@ abstract class BDBStorage implements Storage, Storag } try { - Txn txn = mRepository.openTransactionManager().getTxn(); + Txn txn = mRepository.localTxnManager().getTxn(); return db_compact(txn, mPrimaryDatabase, start, end); } catch (Exception e) { throw mRepository.toRepositoryException(e); @@ -568,7 +618,7 @@ abstract class BDBStorage implements Storage, Storag * @param database database to use */ protected abstract BDBCursor openCursor - (BDBTransactionManager txnMgr, + (TransactionManager txnMgr, byte[] startBound, boolean inclusiveStart, byte[] endBound, boolean inclusiveEnd, int maxPrefix, @@ -588,8 +638,8 @@ abstract class BDBStorage implements Storage, Storag return mRepository.toRepositoryException(e); } - BDBTransactionManager openTransactionManager() { - return mRepository.openTransactionManager(); + TransactionManager localTxnManager() { + return mRepository.localTxnManager(); } /** @@ -647,7 +697,7 @@ abstract class BDBStorage implements Storage, Storag * prevent threads from starting work that will likely fail along the way. */ void checkClosed() throws FetchException { - BDBTransactionManager txnMgr = openTransactionManager(); + TransactionManager txnMgr = localTxnManager(); // Lock out shutdown task. txnMgr.getLock().lock(); @@ -668,7 +718,7 @@ abstract class BDBStorage implements Storage, Storag } void close() throws Exception { - BDBTransactionManager txnMgr = mRepository.openTransactionManager(); + TransactionManager txnMgr = mRepository.localTxnManager(); txnMgr.getLock().lock(); try { if (mPrimaryDatabase != null) { @@ -811,7 +861,7 @@ abstract class BDBStorage implements Storage, Storag } public byte[] tryLoad(byte[] key) throws FetchException { - BDBTransactionManager txnMgr = mStorage.openTransactionManager(); + TransactionManager txnMgr = mStorage.localTxnManager(); byte[] result; // Lock out shutdown task. txnMgr.getLock().lock(); @@ -834,7 +884,7 @@ abstract class BDBStorage implements Storage, Storag } public boolean tryInsert(S storable, byte[] key, byte[] value) throws PersistException { - BDBTransactionManager txnMgr = mStorage.openTransactionManager(); + TransactionManager txnMgr = mStorage.localTxnManager(); Object result; // Lock out shutdown task. txnMgr.getLock().lock(); @@ -857,7 +907,7 @@ abstract class BDBStorage implements Storage, Storag } public void store(S storable, byte[] key, byte[] value) throws PersistException { - BDBTransactionManager txnMgr = mStorage.openTransactionManager(); + TransactionManager txnMgr = mStorage.localTxnManager(); // Lock out shutdown task. txnMgr.getLock().lock(); try { @@ -874,7 +924,7 @@ abstract class BDBStorage implements Storage, Storag } public boolean tryDelete(byte[] key) throws PersistException { - BDBTransactionManager txnMgr = mStorage.openTransactionManager(); + TransactionManager txnMgr = mStorage.localTxnManager(); // Lock out shutdown task. txnMgr.getLock().lock(); try { @@ -907,7 +957,11 @@ abstract class BDBStorage implements Storage, Storag public SequenceValueProducer getSequenceValueProducer(String name) throws PersistException { - return mStorage.mRepository.getSequenceValueProducer(name); + try { + return mStorage.mRepository.getSequenceValueProducer(name); + } catch (RepositoryException e) { + throw e.toPersistException(); + } } public Trigger getInsertTrigger() { diff --git a/src/main/java/com/amazon/carbonado/sequence/AbstractSequenceValueProducer.java b/src/main/java/com/amazon/carbonado/sequence/AbstractSequenceValueProducer.java new file mode 100644 index 0000000..8deb3d7 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/sequence/AbstractSequenceValueProducer.java @@ -0,0 +1,79 @@ +/* + * 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.sequence; + +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/sequence/SequenceCapability.java b/src/main/java/com/amazon/carbonado/sequence/SequenceCapability.java new file mode 100644 index 0000000..0e26eec --- /dev/null +++ b/src/main/java/com/amazon/carbonado/sequence/SequenceCapability.java @@ -0,0 +1,38 @@ +/* + * 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.sequence; + +import com.amazon.carbonado.RepositoryException; +import com.amazon.carbonado.capability.Capability; + +/** + * Capability to use sequences. + * + * @author bcastill + * + */ +public interface SequenceCapability extends Capability { + + /** + * Retrieve and/or generate a SequenceValueProducer for the given name. + * + * @param name sequence name + */ + SequenceValueProducer getSequenceValueProducer(String name) throws RepositoryException; +} diff --git a/src/main/java/com/amazon/carbonado/sequence/SequenceValueGenerator.java b/src/main/java/com/amazon/carbonado/sequence/SequenceValueGenerator.java new file mode 100644 index 0000000..1371a32 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/sequence/SequenceValueGenerator.java @@ -0,0 +1,321 @@ +/* + * 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.sequence; + +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 + * @author bcastill + * @see com.amazon.carbonado.Sequence + * @see StoredSequence + */ +public class SequenceValueGenerator extends AbstractSequenceValueProducer { + public static final int DEFAULT_RESERVE_AMOUNT = 100; + public static final int DEFAULT_INITIAL_VALUE = 1; + public static final int DEFAULT_INCREMENT = 1; + + private final Repository mRepository; + private final Storage 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()) { + // Create a new sequence. + + mStoredSequence.setInitialValue(initialValue); + // Start as small as possible to allow signed long comparisons to work. + mStoredSequence.setNextValue(Long.MIN_VALUE); + + // Try to transfer values from a deprecated sequence. + com.amazon.carbonado.spi.StoredSequence oldSequence; + try { + oldSequence = repo + .storageFor(com.amazon.carbonado.spi.StoredSequence.class).prepare(); + oldSequence.setName(name); + if (oldSequence.tryLoad()) { + mStoredSequence.setInitialValue(oldSequence.getInitialValue()); + mStoredSequence.setNextValue(oldSequence.getNextValue()); + } else { + oldSequence = null; + } + } catch (RepositoryException e) { + // Okay, perhaps no old sequence. + oldSequence = null; + } + + if (mStoredSequence.tryInsert()) { + if (oldSequence != null) { + try { + // Get rid of deprecated sequence. + oldSequence.tryDelete(); + } catch (RepositoryException e) { + // Oh well. + } + } + } else { + // A race condition likely. Load again. + 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. + * + *

    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. + * + *

    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. + * + *

    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/sequence/SequenceValueProducer.java b/src/main/java/com/amazon/carbonado/sequence/SequenceValueProducer.java new file mode 100644 index 0000000..5f7fa03 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/sequence/SequenceValueProducer.java @@ -0,0 +1,103 @@ +/* + * 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.sequence; + +import com.amazon.carbonado.FetchException; +import com.amazon.carbonado.PersistException; + +/** + * Produces values for sequences. + * + * @author Brian S O'Neill + * @author bcastill + * @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. + * + *

    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. + * + *

    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. + * + *

    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. + * + *

    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; + + /** + * 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. + * + *

    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; +} diff --git a/src/main/java/com/amazon/carbonado/sequence/SequenceValueProducerPool.java b/src/main/java/com/amazon/carbonado/sequence/SequenceValueProducerPool.java new file mode 100644 index 0000000..9b9cb8c --- /dev/null +++ b/src/main/java/com/amazon/carbonado/sequence/SequenceValueProducerPool.java @@ -0,0 +1,77 @@ +/* + * 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.sequence; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.amazon.carbonado.RepositoryException; +import com.amazon.carbonado.util.AbstractPool; + +/** + * A concurrent pool of strongly referenced {@link SequenceValueProducer} + * instances mapped by name. SequenceValueProducer instances are lazily created + * and pooled. + * + * @author bcastill + * @author Brian S O'Neill + */ +public abstract class SequenceValueProducerPool + extends AbstractPool +{ + public SequenceValueProducerPool() { + } + + /** + * Returns a SequenceValueProducer instance for the given name, which is + * lazily created and pooled. If multiple threads are requesting upon the + * same name concurrently, at most one thread attempts to lazily create the + * SequenceValueProducer. The others wait for it to become available. + * + * @param name name of sequence + */ + public SequenceValueProducer get(String name) throws RepositoryException { + return (SequenceValueProducer) super.get(name); + } + + /** + * Returns reserved values for all {@link SequenceValueProducer}s. + * + * @param log optional log to report errors; uses default log if null + */ + public void returnReservedValues(Log log) { + for (SequenceValueProducer producer : values()) { + try { + producer.returnReservedValues(); + } catch (RepositoryException e) { + if (log == null) { + log = LogFactory.getLog(SequenceValueProducerPool.class); + } + log.error(e.getMessage(), e); + } + } + } + + protected final SequenceValueProducer create(String name) throws RepositoryException { + return createSequenceValueProducer(name); + } + + protected abstract SequenceValueProducer createSequenceValueProducer(String name) + throws RepositoryException; +} diff --git a/src/main/java/com/amazon/carbonado/sequence/StoredSequence.java b/src/main/java/com/amazon/carbonado/sequence/StoredSequence.java new file mode 100644 index 0000000..3fa770e --- /dev/null +++ b/src/main/java/com/amazon/carbonado/sequence/StoredSequence.java @@ -0,0 +1,70 @@ +/* + * 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.sequence; + +import com.amazon.carbonado.Alias; +import com.amazon.carbonado.Authoritative; +import com.amazon.carbonado.Independent; +import com.amazon.carbonado.PrimaryKey; +import com.amazon.carbonado.Storable; +import com.amazon.carbonado.Version; + +/** + * Stores data for {@link SequenceValueGenerator}. To use with JDBC repository, + * create a table like so: + * + *

    + * CREATE TABLE CARBONADO_SEQUENCE (
    + *     NAME           VARCHAR(100) PRIMARY KEY,
    + *     INITIAL_VALUE  BIGINT       NOT NULL,
    + *     NEXT_VALUE     BIGINT       NOT NULL
    + * )
    + * 
    + * + * @author Brian S O'Neill + */ +@PrimaryKey("name") +@Authoritative +@Independent +@Alias("CARBONADO_SEQUENCE") +public interface StoredSequence extends Storable { + @Alias("NAME") + String getName(); + void setName(String name); + + /** + * Returns the initial value for the sequence. + */ + @Alias("INITIAL_VALUE") + long getInitialValue(); + void setInitialValue(long value); + + /** + * Returns the pre-adjusted next value of the sequence. This value is + * initially Long.MIN_VALUE, and it increments up to Long.MAX_VALUE. The actual + * next value for the sequence is: (getNextValue() + Long.MIN_VALUE + getInitialValue()). + */ + @Alias("NEXT_VALUE") + long getNextValue(); + void setNextValue(long value); + + @Version + int getVersion(); + void setVersion(int version); +} diff --git a/src/main/java/com/amazon/carbonado/sequence/package-info.java b/src/main/java/com/amazon/carbonado/sequence/package-info.java new file mode 100644 index 0000000..d464166 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/sequence/package-info.java @@ -0,0 +1,26 @@ +/* + * 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. + */ + +/** + * Support for generating sequences of values, intended for creating surrogate + * keys. Most repository implementations support sequences already, but direct + * control over sequences might be desired. + * + * @see com.amazon.carbonado.Sequence + */ +package com.amazon.carbonado.sequence; 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 Transaction type + * @author Brian S O'Neill + */ +public abstract class AbstractRepository + implements Repository, ShutdownCapability, SequenceCapability +{ + private final String mName; + private final ReadWriteLock mShutdownLock; + + private final ThreadLocal> mCurrentTxnMgr; + + // Weakly tracks all TransactionManager instances for shutdown hook. + private final Map, ?> 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>(); + mAllTxnMgrs = new WeakIdentityMap(); + + mStoragePool = new StoragePool() { + protected Storage createStorage(Class 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 Storage storageFor(Class 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 getCapability(Class 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 localTransactionManager() { + TransactionManager 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 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 createTransactionManager(); + + /** + * Called upon to create a new Storage instance. + */ + protected abstract Storage createStorage(Class 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> mRepository; + + ShutdownHook(AbstractRepository repository) { + super(repository.getClass().getSimpleName() + " shutdown (" + + repository.getName() + ')'); + mRepository = new WeakReference>(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 return mRepo; } + public void truncate() { + throw error(); + } + public boolean addTrigger(Trigger 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(), 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); @@ -185,6 +195,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 @@ -381,6 +423,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. */ @@ -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 mLobStorage; final Storage 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 { ordinal++; if (property.isJoin() || property.isPrimaryKeyMember() - || property.isNullable()) + || property.isNullable() + || property.isAutomatic() || property.isVersion()) { continue; } @@ -808,63 +811,25 @@ public final class MasterStorableGenerator { 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 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. - * - *

    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. - * - *

    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. - * - *

    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. - * - *

    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. - * - *

    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. - * - *

    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. - * - *

    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 { 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 { } } - 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 { (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 { // 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 { // 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 { // 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 { // 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 { // 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 { // 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 { // 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 { // 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 { } // 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 { } 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 { // 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 { 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 { 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 { } 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 { } 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 { private void addIsInitializedMethod (String name, Map> properties) { + // Don't check Automatic properties. + { + boolean cloned = false; + for (StorableProperty prop : properties.values()) { + if (prop.isAutomatic() || prop.isVersion()) { + if (!cloned) { + properties = new LinkedHashMap>(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 { 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 { * @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 { */ 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 { } 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 { 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 { 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 @@ -200,6 +200,26 @@ public class StorableIndexSet extends TreeSet, StorableIndex> replacements = null; + for (StorableIndex index : this) { + StorableIndex replacement = index.clustered(clustered); + if (replacement != index) { + if (replacements == null) { + replacements = new HashMap, StorableIndex>(); + } + 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, Storage> mStorageMap; - private final ConcurrentMap, Object> mStorableTypeLockMap; - - public StorageCollection() { - mStorageMap = new ConcurrentHashMap, Storage>(); - mStorableTypeLockMap = new ConcurrentHashMap, Object>(); - } - - public Storage storageFor(Class 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 allStorage() { - return mStorageMap.values(); - } - - protected abstract Storage createStorage(Class 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 +{ + 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 Storage getStorage(Class type) + throws MalformedTypeException, SupportException, RepositoryException + { + return (Storage) super.get(type); + } + + protected final Storage create(Class type) throws SupportException, RepositoryException { + return createStorage(type); + } + + protected abstract Storage createStorage(Class 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 { - @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 implements Storage { return wrap(mStorage.query(filter)); } + public void truncate() throws PersistException { + mStorage.truncate(); + } + public boolean addTrigger(Trigger trigger) { return mTriggerManager.addTrigger(trigger); } diff --git a/src/main/java/com/amazon/carbonado/synthetic/SyntheticStorableReferenceBuilder.java b/src/main/java/com/amazon/carbonado/synthetic/SyntheticStorableReferenceBuilder.java index 78bf22e..fb8b1fc 100644 --- a/src/main/java/com/amazon/carbonado/synthetic/SyntheticStorableReferenceBuilder.java +++ b/src/main/java/com/amazon/carbonado/synthetic/SyntheticStorableReferenceBuilder.java @@ -25,10 +25,8 @@ import java.util.Comparator; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; import java.util.Set; -import com.amazon.carbonado.FetchException; import com.amazon.carbonado.Storable; import com.amazon.carbonado.SupportException; import com.amazon.carbonado.info.Direction; diff --git a/src/main/java/com/amazon/carbonado/util/AbstractPool.java b/src/main/java/com/amazon/carbonado/util/AbstractPool.java new file mode 100644 index 0000000..896802d --- /dev/null +++ b/src/main/java/com/amazon/carbonado/util/AbstractPool.java @@ -0,0 +1,94 @@ +/* + * 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.util; + +import java.util.Collection; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.Lock; + +/** + * A concurrent pool of strongly referenced values mapped by key. Values are + * lazily created and pooled. + * + * @author Brian S O'Neill + */ +public abstract class AbstractPool { + private final ConcurrentMap mValues; + private final WeakReentrantLockPool mLockPool; + + protected AbstractPool() { + mValues = new ConcurrentHashMap(); + mLockPool = new WeakReentrantLockPool(); + } + + /** + * Returns a value for the given key, which is lazily created and + * pooled. If multiple threads are requesting upon the same key + * concurrently, at most one thread attempts to lazily create the + * value. The others wait for it to become available. + */ + public V get(K key) throws E { + // Quick check without locking. + V value = mValues.get(key); + if (value != null) { + return value; + } + + // Check again with key lock held. + Lock lock = mLockPool.get(key); + lock.lock(); + try { + value = mValues.get(key); + if (value == null) { + try { + value = create(key); + mValues.put(key, value); + } catch (Exception e) { + // Workaround compiler bug. + ThrowUnchecked.fire(e); + } + } + } finally { + lock.unlock(); + } + + return value; + } + + /** + * Remove a value, returning the old value. + */ + public V remove(Object key) { + return mValues.remove(key); + } + + /** + * Returns the pool values, which may be concurrently modified. + */ + public Collection values() { + return mValues.values(); + } + + /** + * Return a new value instance. + */ + protected abstract V create(K key) throws E; +} diff --git a/src/main/java/com/amazon/carbonado/util/AbstractWeakPool.java b/src/main/java/com/amazon/carbonado/util/AbstractWeakPool.java new file mode 100644 index 0000000..8ae5476 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/util/AbstractWeakPool.java @@ -0,0 +1,143 @@ +/* + * 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.util; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * A concurrent pool of weakly referenced values mapped by key. Values are + * created (and recreated) as needed. + * + * @author Brian S O'Neill + * @see AbstractPool + */ +abstract class AbstractWeakPool { + private final ConcurrentMap> mValues; + private final ReferenceQueue mValueRefQueue; + + protected AbstractWeakPool() { + mValues = new ConcurrentHashMap>(); + mValueRefQueue = new ReferenceQueue(); + } + + /** + * Returns a value for the given key. Unused values are automatically + * cleared to free up memory, even if they are still held. Repeat calls are + * guaranteed to return the same value instance only if the value is + * strongly reachable. The following idiom should be used when using the + * pool for maintaining locks: + * + *

    +     * // Store lock in local variable to be strongly reachable.
    +     * Lock lock = lockPool.get(key);
    +     * lock.lock();
    +     * try {
    +     *     // access the resource protected by this lock
    +     *     ...
    +     * } finally {
    +     *     lock.unlock();
    +     * }
    +     * 
    + */ + public V get(K key) throws E { + clean(); + + ValueRef valueRef = mValues.get(key); + V value; + + if (valueRef == null || (value = valueRef.get()) == null) { + try { + value = create(key); + } catch (Exception e) { + // Workaround compiler bug. + ThrowUnchecked.fire(e); + return null; + } + valueRef = new ValueRef(value, mValueRefQueue, key); + while (true) { + ValueRef existingRef = mValues.putIfAbsent(key, valueRef); + if (existingRef == null) { + // Newly created value is now the official value. + break; + } + V existing = existingRef.get(); + if (existing != null) { + // Someone else just created value before us. Use that + // instead and chuck the new value object. + value = existing; + valueRef.clear(); + break; + } + // Reference just got cleared. Try again. Explicitly remove it + // to prevent an infinite loop. Note that the two argument + // remove method is called to ensure that what is being removed + // is not a new value. + mValues.remove(((ValueRef) existingRef).mKey, existingRef); + } + } + + return value; + } + + /** + * Manually remove a value, returning the old value. + */ + public V remove(Object key) { + clean(); + + ValueRef valueRef = mValues.remove(key); + V value; + + if (valueRef != null && (value = valueRef.get()) != null) { + valueRef.clear(); + return value; + } + + return null; + } + + /** + * Return a new value instance. + */ + protected abstract V create(K key) throws E; + + private void clean() { + // Clean out cleared values. + Reference ref; + while ((ref = mValueRefQueue.poll()) != null) { + // Note that the two argument remove method is called to ensure + // that what is being removed is not a new value. + mValues.remove(((ValueRef) ref).mKey, ref); + } + } + + private static class ValueRef extends WeakReference { + final K mKey; + + ValueRef(V value, ReferenceQueue queue, K key) { + super(value, queue); + mKey = key; + } + } +} diff --git a/src/main/java/com/amazon/carbonado/util/WeakReentrantLockPool.java b/src/main/java/com/amazon/carbonado/util/WeakReentrantLockPool.java new file mode 100644 index 0000000..5bfd58c --- /dev/null +++ b/src/main/java/com/amazon/carbonado/util/WeakReentrantLockPool.java @@ -0,0 +1,45 @@ +/* + * 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.util; + +import java.util.concurrent.locks.ReentrantLock; + +/** + * A concurrent pool of weakly referenced {@link ReentrantLock} instances + * mapped by key. Locks are created (and recreated) as needed. + * + * @author Brian S O'Neill + */ +class WeakReentrantLockPool + extends AbstractWeakPool +{ + private final boolean mFair; + + public WeakReentrantLockPool() { + this(false); + } + + public WeakReentrantLockPool(boolean fair) { + mFair = fair; + } + + protected ReentrantLock create(K key) { + return new ReentrantLock(mFair); + } +} diff --git a/src/main/java/com/amazon/carbonado/util/WeakReentrantReadWriteLockPool.java b/src/main/java/com/amazon/carbonado/util/WeakReentrantReadWriteLockPool.java new file mode 100644 index 0000000..b41ad1f --- /dev/null +++ b/src/main/java/com/amazon/carbonado/util/WeakReentrantReadWriteLockPool.java @@ -0,0 +1,45 @@ +/* + * 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.util; + +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * A concurrent pool of weakly referenced {@link ReentrantReadWriteLock} + * instances mapped by key. Locks are created (and recreated) as needed. + * + * @author Brian S O'Neill + */ +class WeakReentrantReadWriteLockPool + extends AbstractWeakPool +{ + private final boolean mFair; + + public WeakReentrantReadWriteLockPool() { + this(false); + } + + public WeakReentrantReadWriteLockPool(boolean fair) { + mFair = fair; + } + + protected ReentrantReadWriteLock create(K key) { + return new ReentrantReadWriteLock(mFair); + } +} -- cgit v1.2.3