diff options
author | Brian S. O'Neill <bronee@gmail.com> | 2007-03-28 22:00:24 +0000 |
---|---|---|
committer | Brian S. O'Neill <bronee@gmail.com> | 2007-03-28 22:00:24 +0000 |
commit | 8809341248c62b15b78d7e6d8e06ab2ec3793c8e (patch) | |
tree | f39d7353987e025758e0a3abe1ffb49a48e9be9e /src/main/java | |
parent | 65478f17ada9df04c4c2afa734378bb50ec2bd13 (diff) |
Merged 1.2-dev to trunk.
Diffstat (limited to 'src/main/java')
74 files changed, 2979 insertions, 1228 deletions
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.
+ *
+ * <p>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.
+ *
+ * <p>Example:<pre>
+ * @PrimaryKey("userInfoID")
+ * public interface UserInfo extends Storable<UserInfo> {
+ * <b>@Automatic</b>
+ * long getUserInfoID();
+ * void setUserInfoID(long id);
+ *
+ * ...
+ * }
+ * </pre>
+ *
+ * @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 @@ -112,6 +112,16 @@ public interface Storage<S extends Storable> { Query<S> query(Filter<S> filter) throws FetchException;
/**
+ * Attempts to quickly delete all Storables instances in this
+ * Storage. Support for transactional truncation is not guaranteed.
+ *
+ * <p>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
* after all other triggers. In other words, it is added at the outermost
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 @@ -38,6 +38,27 @@ import com.amazon.carbonado.filter.OpenFilter; */
public abstract class FilteredCursor<S> extends AbstractCursor<S> {
/**
+ * 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 <S extends Storable> Cursor<S> applyFilter(Cursor<S> cursor,
+ Class<S> type,
+ String filter,
+ Object... filterValues)
+ {
+ Filter<S> f = Filter.filterFor(type, filter).bind();
+ FilterValues<S> 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
* instances as used to construct the FilterValues. An
@@ -61,6 +82,9 @@ public abstract class FilteredCursor<S> extends AbstractCursor<S> { 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 @@ -165,6 +165,25 @@ public class ChainedProperty<S extends Storable> implements Appender { }
/**
+ * 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<S extends Storable> 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<S extends Storable> 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<S>
(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<S>
(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<S> 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<? extends Storable> 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,10 +184,19 @@ public interface StorableProperty<S extends Storable> 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<S extends Storable> implements Storabl private final int mPkPropertyCount;
private final InstanceFactory mInstanceFactory;
+ // Modified by CustomStorableCodecFactory after construction. This provides
+ // backwards compatibility with implementations of CustomStorableCodecFactory.
+ RawSupport<S> mSupport;
+
public interface InstanceFactory {
Storable instantiate(RawSupport support, CustomStorableCodec codec);
@@ -245,11 +249,22 @@ public abstract class CustomStorableCodec<S extends Storable> implements Storabl * @throws SupportException if Storable is not supported
*/
public CustomStorableCodec(Class<S> 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<S> type, boolean isMaster, RawSupport<S> support)
+ throws SupportException
+ {
mType = type;
mPkPropertyCount = getPrimaryKeyIndex().getPropertyCount();
Class<? extends S> storableClass = getStorableClass(type, isMaster);
mInstanceFactory = QuickConstructorGenerator
.getInstance(storableClass, InstanceFactory.class);
+ mSupport = support;
}
public Class<S> getStorableType() {
@@ -257,6 +272,18 @@ public abstract class CustomStorableCodec<S extends Storable> implements Storabl }
@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<S> support) {
return (S) mInstanceFactory.instantiate(support, this);
}
@@ -276,6 +303,18 @@ public abstract class CustomStorableCodec<S extends Storable> implements Storabl return encodePrimaryKey(values, 0, mPkPropertyCount);
}
+ public RawSupport<S> getSupport() {
+ return mSupport;
+ }
+
+ private RawSupport<S> support() {
+ RawSupport<S> 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 @@ -59,6 +59,30 @@ public abstract class CustomStorableCodecFactory implements StorableCodecFactory /**
* @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 <S extends Storable> CustomStorableCodec<S> createCodec(Class<S> type,
+ StorableIndex pkIndex,
+ boolean isMaster,
+ Layout layout,
+ RawSupport support)
+ throws SupportException
+ {
+ CustomStorableCodec<S> 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
* @param layout when non-null, attempt to encode a storable layout
* generation value in each storable
@@ -67,4 +91,20 @@ public abstract class CustomStorableCodecFactory implements StorableCodecFactory protected abstract <S extends Storable> CustomStorableCodec<S>
createCodec(Class<S> 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 <S extends Storable> CustomStorableCodec<S>
+ createCodec(Class<S> 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<S extends Storable> { * 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<S extends Storable> implements StorableCodec<S * @param layout when non-null, encode a storable layout generation
* value in one or four bytes. Generation 0..127 is encoded in one byte, and
* 128..max is encoded in four bytes, with the most significant bit set.
+ * @param support binds generated storable with a storage layer
* @throws SupportException if Storable is not supported
* @throws amazon.carbonado.MalformedTypeException if Storable type is not well-formed
* @throws IllegalArgumentException if type is null
@@ -86,29 +87,25 @@ public class GenericStorableCodec<S extends Storable> implements StorableCodec<S @SuppressWarnings("unchecked")
static synchronized <S extends Storable> GenericStorableCodec<S> getInstance
(GenericStorableCodecFactory factory,
- GenericEncodingStrategy<S> encodingStrategy, boolean isMaster, Layout layout)
+ GenericEncodingStrategy<S> 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<S> codec = (GenericStorableCodec<S>) cCache.get(key);
- if (codec == null) {
- codec = new GenericStorableCodec<S>
- (factory,
- encodingStrategy.getType(),
- generateStorable(encodingStrategy, isMaster, layout),
- encodingStrategy,
- layout);
- cCache.put(key, codec);
+ Class<? extends S> storableImpl = (Class<? extends S>) cCache.get(key);
+ if (storableImpl == null) {
+ storableImpl = generateStorable(encodingStrategy, isMaster, layout);
+ cCache.put(key, storableImpl);
}
- return codec;
+ return new GenericStorableCodec<S>
+ (factory,
+ encodingStrategy.getType(),
+ storableImpl,
+ encodingStrategy,
+ layout,
+ support);
}
@SuppressWarnings("unchecked")
@@ -325,10 +322,7 @@ public class GenericStorableCodec<S extends Storable> implements StorableCodec<S private final Class<? extends S> 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<GenericEncodingStrategy<S>> mEncodingStrategy;
+ private final GenericEncodingStrategy<S> mEncodingStrategy;
private final GenericInstanceFactory mInstanceFactory;
@@ -339,27 +333,30 @@ public class GenericStorableCodec<S extends Storable> implements StorableCodec<S private final Layout mLayout;
+ private final RawSupport<S> mSupport;
+
// Maps layout generations to Decoders.
private IntHashMap mDecoders;
private GenericStorableCodec(GenericStorableCodecFactory factory,
Class<S> type, Class<? extends S> storableClass,
GenericEncodingStrategy<S> encodingStrategy,
- Layout layout) {
+ Layout layout, RawSupport<S> support)
+ {
mFactory = factory;
mType = type;
mStorableClass = storableClass;
- mEncodingStrategy = new WeakReference<GenericEncodingStrategy<S>>(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<S extends Storable> implements StorableCodec<S }
/**
- * 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
+ */
+ @SuppressWarnings("unchecked")
+ public S instantiate() {
+ return (S) mInstanceFactory.instantiate(support());
+ }
+
+ /**
+ * 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
+ */
+ @SuppressWarnings("unchecked")
+ public S instantiate(byte[] key, byte[] value) throws FetchException {
+ return (S) mInstanceFactory.instantiate(support(), key, value);
+ }
+
+ /**
+ * 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
*/
@@ -385,14 +405,13 @@ public class GenericStorableCodec<S extends Storable> implements StorableCodec<S }
/**
- * 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
*/
@SuppressWarnings("unchecked")
- public S instantiate(RawSupport<S> support, byte[] key, byte[] value)
- throws FetchException
- {
+ public S instantiate(RawSupport<S> 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<S extends Storable> implements StorableCodec<S }
public StorableIndex<S> 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<S extends Storable> implements StorableCodec<S return mPrimaryKeyFactory.encodeSearchKeyPrefix();
}
+ public RawSupport<S> getSupport() {
+ return mSupport;
+ }
+
+ private RawSupport<S> support() {
+ RawSupport<S> 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<S extends Storable> implements StorableCodec<S }
}
- private GenericEncodingStrategy<S> 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<S> generateSearchKeyFactory(OrderedProperty<S>[] properties) {
- GenericEncodingStrategy encodingStrategy = getEncodingStrategy();
-
ClassInjector ci;
{
StringBuilder b = new StringBuilder();
@@ -562,7 +584,7 @@ public class GenericStorableCodec<S extends Storable> implements StorableCodec<S // useReadMethods = false (will read fields directly)
// partialStartVar = null (only support encoding all properties)
// partialEndVar = null (only support encoding all properties)
- LocalVariable encodedVar = encodingStrategy.buildKeyEncoding
+ LocalVariable encodedVar = mEncodingStrategy.buildKeyEncoding
(b, properties, instanceVar, null, false, null, null);
b.loadLocal(encodedVar);
@@ -591,7 +613,7 @@ public class GenericStorableCodec<S extends Storable> implements StorableCodec<S // useReadMethods = false (will read fields directly)
// partialStartVar = int parameter 1, references start property index
// partialEndVar = int parameter 2, references end property index
- LocalVariable encodedVar = encodingStrategy.buildKeyEncoding
+ LocalVariable encodedVar = mEncodingStrategy.buildKeyEncoding
(b, properties, instanceVar, null, false, b.getParameter(1), b.getParameter(2));
b.loadLocal(encodedVar);
@@ -623,7 +645,7 @@ public class GenericStorableCodec<S extends Storable> implements StorableCodec<S // useReadMethods = false (will read fields directly)
// partialStartVar = null (only support encoding all properties)
// partialEndVar = null (only support encoding all properties)
- LocalVariable encodedVar = encodingStrategy.buildKeyEncoding
+ LocalVariable encodedVar = mEncodingStrategy.buildKeyEncoding
(b, properties, b.getParameter(0), adapterInstanceClass, false, null, null);
b.loadLocal(encodedVar);
@@ -648,7 +670,7 @@ public class GenericStorableCodec<S extends Storable> implements StorableCodec<S // useReadMethods = false (will read fields directly)
// partialStartVar = int parameter 1, references start property index
// partialEndVar = int parameter 2, references end property index
- LocalVariable encodedVar = encodingStrategy.buildKeyEncoding
+ LocalVariable encodedVar = mEncodingStrategy.buildKeyEncoding
(b, properties, b.getParameter(0), adapterInstanceClass,
false, b.getParameter(1), b.getParameter(2));
@@ -666,8 +688,8 @@ public class GenericStorableCodec<S extends Storable> implements StorableCodec<S (Modifiers.PUBLIC, "encodeSearchKeyPrefix", byteArrayType, null);
CodeBuilder b = new CodeBuilder(mi);
- if (encodingStrategy.getKeyPrefixPadding() == 0 &&
- encodingStrategy.getKeySuffixPadding() == 0) {
+ if (mEncodingStrategy.getKeyPrefixPadding() == 0 &&
+ mEncodingStrategy.getKeySuffixPadding() == 0) {
// Return null instead of a zero-length array.
b.loadNull();
b.returnValue(byteArrayType);
@@ -689,7 +711,7 @@ public class GenericStorableCodec<S extends Storable> implements StorableCodec<S // useReadMethods = false (no parameters means we don't need this)
// partialStartVar = null (no parameters means we don't need this)
// partialEndVar = null (no parameters means we don't need this)
- LocalVariable encodedVar = encodingStrategy.buildKeyEncoding
+ LocalVariable encodedVar = mEncodingStrategy.buildKeyEncoding
(b, new OrderedProperty[0], null, null, false, null, null);
b.loadLocal(encodedVar);
diff --git a/src/main/java/com/amazon/carbonado/raw/GenericStorableCodecFactory.java b/src/main/java/com/amazon/carbonado/raw/GenericStorableCodecFactory.java index ab640e6..3885505 100644 --- a/src/main/java/com/amazon/carbonado/raw/GenericStorableCodecFactory.java +++ b/src/main/java/com/amazon/carbonado/raw/GenericStorableCodecFactory.java @@ -57,8 +57,29 @@ public class GenericStorableCodecFactory implements StorableCodecFactory { Layout layout)
throws SupportException
{
+ return createCodec(type, pkIndex, isMaster, layout, null);
+ }
+
+ /**
+ * @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, encode a storable layout generation
+ * value in one or four bytes. Generation 0..127 is encoded in one byte, and
+ * 128..max is encoded in four bytes, with the most significant bit set.
+ * @param support binds generated storable with a storage layer
+ * @throws SupportException if type is not supported
+ */
+ @SuppressWarnings("unchecked")
+ public <S extends Storable> GenericStorableCodec<S> createCodec(Class<S> 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<S extends Storable> { Class<S> 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<S> 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<S> support, byte[] key, byte[] value)
- throws FetchException;
+ S instantiate(RawSupport<S> 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<S extends Storable> { * 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<S> 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
+ */
+ <S extends Storable> StorableCodec<S> createCodec(Class<S> 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<Repository> 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 <S extends Storable> Storage<S> createStorage(Class<S> type)
throws RepositoryException
{
@@ -112,7 +115,7 @@ class IndexedRepository implements Repository, public <S extends Storable> Storage<S> storageFor(Class<S> 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<String> 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<S extends Storable> implements Storage<S>, StorageAccess<S> // 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<S extends Storable> implements Storage<S>, StorageAccess<S> // 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<S extends Storable> implements Storage<S>, StorageAccess<S> 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<? super S> trigger) {
return mMasterStorage.addTrigger(trigger);
}
@@ -372,9 +410,6 @@ class IndexedStorage<S extends Storable> implements Storage<S>, StorageAccess<S> }
}
- // 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<S>(mRootStorage);
}
@@ -521,20 +556,24 @@ class IndexedStorage<S extends Storable> implements Storage<S>, StorageAccess<S> {
// 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<? extends Storable> 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<? extends Storable> 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<S extends Storable> implements Storage<S>, StorageAccess<S> } 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<S extends Storable> extends AbstractCursor<S> { private final JDBCStorage<S> 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<S> 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<S extends Storable> extends AbstractCursor<S> { }
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<JDBCTransaction>
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<TriggerFactory> mTriggerFactories;
private final AtomicReference<Repository> 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<String, Boolean> mAutoVersioningMap;
// Track all open connections so that they can be closed when this
// repository is closed.
private Map<Connection, Object> mOpenConnections;
-
- private final ThreadLocal<JDBCTransactionManager> mCurrentTxnMgr;
-
- // Weakly tracks all JDBCTransactionManager instances for shutdown.
- private final Map<JDBCTransactionManager, ?> 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<Repository> rootRef,
String name, boolean isMaster,
Iterable<TriggerFactory> triggerFactories,
- DataSource dataSource, String catalog, String schema)
+ DataSource dataSource, boolean dataSourceClose,
+ String catalog, String schema,
+ Map<String, Boolean> 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 <S extends Storable> Storage<S> createStorage(Class<S> type)
- throws RepositoryException
- {
- // Lock on mAllTxnMgrs to prevent databases from being opened during shutdown.
- synchronized (mAllTxnMgrs) {
- JDBCStorableInfo<S> info = examineStorable(type);
- if (!info.isSupported()) {
- throw new UnsupportedTypeException("Independent type not supported", type);
- }
- return new JDBCStorage<S>(JDBCRepository.this, info);
- }
- }
- };
+ mAutoVersioningMap = autoVersioningMap;
mOpenConnections = new IdentityHashMap<Connection, Object>();
- mCurrentTxnMgr = new ThreadLocal<JDBCTransactionManager>();
- 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 <S extends Storable> Storage<S> storageFor(Class<S> 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 extends Capability> C getCapability(Class<C> capabilityType) {
- if (capabilityType.isInstance(this)) {
- return (C) this;
- }
- return null;
- }
-
public <S extends Storable> IndexInfo[] getIndexInfo(Class<S> 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<String> names = new ArrayList<String>();
- 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<JDBCTransaction> createTransactionManager() {
+ return new JDBCTransactionManager(this);
+ }
+
+ protected <S extends Storable> Storage<S> createStorage(Class<S> type)
+ throws RepositoryException
+ {
+ JDBCStorableInfo<S> 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<S>(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<JDBCTransaction> 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; * <li>{@link com.amazon.carbonado.capability.IndexInfoCapability IndexInfoCapability}
* <li>{@link com.amazon.carbonado.capability.StorableInfoCapability StorableInfoCapability}
* <li>{@link com.amazon.carbonado.capability.ShutdownCapability ShutdownCapability}
+ * <li>{@link com.amazon.carbonado.sequence.SequenceCapability SequenceCapability}
* <li>{@link JDBCConnectionCapability JDBCConnectionCapability}
* </ul>
*
* @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<String, Boolean> 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;
}
@@ -138,6 +149,22 @@ public class JDBCRepositoryBuilder extends AbstractRepositoryBuilder { }
/**
+ * 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.
*
* @see LoggingDataSource
@@ -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<String, Boolean>();
+ }
+ mAutoVersioningMap.put(className, enabled);
+ }
+
+ private Map<String, Boolean> getAutoVersioningMap() {
+ if (mAutoVersioningMap == null) {
+ return null;
+ }
+ return new HashMap<String, Boolean>(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<String> 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<S extends Storable> { // Initial StringBuilder capactity for update statement.
private static final int INITIAL_UPDATE_BUFFER_SIZE = 100;
- private static final Map<Class<?>, Class<? extends Storable>> 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<Object, Class<? extends Storable>> cCache;
static {
cCache = new SoftValuedHashMap();
}
- static <S extends Storable> Class<? extends S> getGeneratedClass(JDBCStorableInfo<S> info)
+ static <S extends Storable> Class<? extends S> getGeneratedClass(JDBCStorableInfo<S> info,
+ boolean autoVersioning)
throws SupportException
{
- Class<S> type = info.getStorableType();
+ Object key = KeyFactory.createKey(new Object[] {info, autoVersioning});
+
synchronized (cCache) {
- Class<? extends S> generatedClass = (Class<? extends S>) cCache.get(type);
+ Class<? extends S> generatedClass = (Class<? extends S>) cCache.get(key);
if (generatedClass != null) {
return generatedClass;
}
- generatedClass = new JDBCStorableGenerator<S>(info).generateAndInjectClass();
- cCache.put(type, generatedClass);
+ generatedClass =
+ new JDBCStorableGenerator<S>(info, autoVersioning).generateAndInjectClass();
+ cCache.put(key, generatedClass);
return generatedClass;
}
}
private final Class<S> mStorableType;
private final JDBCStorableInfo<S> mInfo;
+ private final boolean mAutoVersioning;
private final Map<String, ? extends JDBCStorableProperty<S>> mAllProperties;
private final ClassLoader mParentClassLoader;
private final ClassInjector mClassInjector;
private final ClassFile mClassFile;
- private JDBCStorableGenerator(JDBCStorableInfo<S> info) throws SupportException {
+ private JDBCStorableGenerator(JDBCStorableInfo<S> info, boolean autoVersioning)
+ throws SupportException
+ {
mStorableType = info.getStorableType();
mInfo = info;
+ mAutoVersioning = autoVersioning;
mAllProperties = mInfo.getAllProperties();
EnumSet<MasterFeature> 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<? extends S> abstractClass =
MasterStorableGenerator.getAbstractClass(mStorableType, features);
@@ -127,7 +142,7 @@ class JDBCStorableGenerator<S extends Storable> { mClassFile.setTarget("1.5");
}
- private Class<? extends S> generateAndInjectClass() {
+ private Class<? extends S> 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<S extends Storable> { // 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<ordinal; i++) {
+ if (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<JDBCStorableProperty<S>> 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<S> versionProperty = null;
-
// Gather all Lob properties to track if a post-insert update is required.
Map<JDBCStorableProperty<S>, Integer> lobIndexMap = findLobs();
LocalVariable lobArrayVar = null;
@@ -481,43 +608,85 @@ class JDBCStorableGenerator<S extends Storable> { }
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<S> 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<S extends Storable> { 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<S extends Storable> { 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<S extends Storable> { 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<S extends Storable> { 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<S> 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<S extends Storable> { 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<S extends Storable> { 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<S extends Storable> { for (JDBCStorableProperty<S> 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);
@@ -924,6 +1093,13 @@ class JDBCStorableGenerator<S extends Storable> { }
/**
+ * 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<S extends Storable> { LocalVariable psVar,
LocalVariable jdbcRepoVar,
LocalVariable instanceVar)
+ throws SupportException
{
final TypeDesc superType = TypeDesc.forClass(mClassFile.getSuperClassName());
final Iterable<? extends JDBCStorableProperty<?>> properties =
@@ -1108,28 +1285,19 @@ class JDBCStorableGenerator<S extends Storable> { 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<S extends Storable> { 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<S extends Storable> { 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<S extends Storable> { continue;
}
- Label skipProperty = b.createLabel();
+ Label nextProperty = b.createLabel();
final TypeDesc propertyType = TypeDesc.forClass(property.getType());
@@ -1200,12 +1368,12 @@ class JDBCStorableGenerator<S extends Storable> { // 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<S extends Storable> { * 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<S extends Storable> { * @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<S extends Storable> { }
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<S extends Storable> { }
}
- /**
- * @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<ordinal; i++) {
- if (i > 0) {
- b.append(',');
- }
- b.append('?');
- }
-
- b.append(')');
-
- return versionPropNumber;
- }
-
- private Map<JDBCStorableProperty<S>, Class<?>> generateLobLoaders() {
+ private Map<JDBCStorableProperty<S>, Class<?>> generateLobLoaders()
+ throws SupportException
+ {
Map<JDBCStorableProperty<S>, Class<?>> lobLoaderMap =
new IdentityHashMap<JDBCStorableProperty<S>, Class<?>>();
@@ -1789,7 +1919,9 @@ class JDBCStorableGenerator<S extends Storable> { *
* @param loaderType either JDBCBlobLoader or JDBCClobLoader
*/
- private Class<?> generateLobLoader(JDBCStorableProperty<S> property, Class<?> loaderType) {
+ private Class<?> generateLobLoader(JDBCStorableProperty<S> 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<S extends Storable> extends StorableInfo<S> { Map<String, JDBCStorableProperty<S>> getDataProperties();
+ /**
+ * Returns auto-increment properties which are primary key members. The map
+ * should almost always be empty or contain one property.
+ */
+ Map<String, JDBCStorableProperty<S>> getIdentityProperties();
+
JDBCStorableProperty<S> 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<S>(mainProperty, columnInfo,
+ autoIncrement,
accessInfo.mResultSetGet,
accessInfo.mPreparedStatementSet,
accessInfo.getAdapter());
@@ -974,6 +994,7 @@ public class JDBCStorableIntrospector extends StorableIntrospector { private transient Map<String, JDBCStorableProperty<S>> mPrimaryKeyProperties;
private transient Map<String, JDBCStorableProperty<S>> mDataProperties;
+ private transient Map<String, JDBCStorableProperty<S>> mIdentityProperties;
private transient JDBCStorableProperty<S> mVersionProperty;
JInfo(StorableInfo<S> mainInfo,
@@ -1104,6 +1125,23 @@ public class JDBCStorableIntrospector extends StorableIntrospector { return mDataProperties;
}
+ public Map<String, JDBCStorableProperty<S>> getIdentityProperties() {
+ if (mIdentityProperties == null) {
+ Map<String, JDBCStorableProperty<S>> idProps =
+ new LinkedHashMap<String, JDBCStorableProperty<S>>(1);
+ for (Map.Entry<String, JDBCStorableProperty<S>> entry :
+ getPrimaryKeyProperties().entrySet())
+ {
+ JDBCStorableProperty<S> property = entry.getValue();
+ if (property.isAutoIncrement()) {
+ idProps.put(entry.getKey(), property);
+ }
+ }
+ mIdentityProperties = Collections.unmodifiableMap(idProps);
+ }
+ return mIdentityProperties;
+ }
+
public JDBCStorableProperty<S> getVersionProperty() {
if (mVersionProperty == null) {
for (JDBCStorableProperty<S> 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<S>[] mInternal;
private JDBCStorableProperty<?>[] mExternal;
@@ -1140,9 +1179,13 @@ public class JDBCStorableIntrospector extends StorableIntrospector { /**
* Join properties need to be filled in later.
*/
- JProperty(StorableProperty<S> mainProperty, ColumnInfo columnInfo,
- Method resultSetGet, Method preparedStatementSet,
- StorablePropertyAdapter adapter) {
+ JProperty(StorableProperty<S> 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<S> 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 @@ -46,6 +46,12 @@ public interface JDBCStorableProperty<S extends Storable> 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.
*
* @return null if property is unsupported
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<S extends Storable> extends StandardQueryFactory<S> final TriggerManager<S> mTriggerManager;
- JDBCStorage(JDBCRepository repository, JDBCStorableInfo<S> info)
+ JDBCStorage(JDBCRepository repository, JDBCStorableInfo<S> info, boolean autoVersioning)
throws SupportException, RepositoryException
{
super(info.getStorableType());
@@ -103,7 +95,9 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S> mSupportStrategy = repository.getSupportStrategy();
mInfo = info;
- Class<? extends S> generatedStorableClass = JDBCStorableGenerator.getGeneratedClass(info);
+ Class<? extends S> generatedStorableClass = JDBCStorableGenerator
+ .getGeneratedClass(info, autoVersioning);
+
mInstanceFactory = QuickConstructorGenerator
.getInstance(generatedStorableClass, InstanceFactory.class);
@@ -134,6 +128,33 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S> 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<? super S> trigger) {
return mTriggerManager.addTrigger(trigger);
}
@@ -147,7 +168,11 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S> }
public SequenceValueProducer getSequenceValueProducer(String name) throws PersistException {
- return mSupportStrategy.getSequenceValueProducer(name);
+ try {
+ return mRepository.getSequenceValueProducer(name);
+ } catch (RepositoryException e) {
+ throw e.toPersistException();
+ }
}
public Trigger<? super S> getInsertTrigger() {
@@ -172,7 +197,7 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S> 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<S extends Storable> extends StandardQueryFactory<S> 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<S extends Storable> extends StandardQueryFactory<S> }
public Cursor<S> fetch(FilterValues<S> 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<S>(JDBCStorage.this, con, ps);
+ return new JDBCCursor<S>(JDBCStorage.this, con, ps, scrollInsensitiveReadOnly);
} catch (Exception e) {
// in case of exception, close statement
try {
@@ -519,7 +558,7 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S> 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<S extends Storable> extends StandardQueryFactory<S> 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<String, SequenceValueProducer> 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<String, SequenceValueProducer>();
+ 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> T unwrap(Class<T> 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;
@@ -127,13 +131,6 @@ class OracleSupportStrategy extends JDBCSupportStrategy { }
@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> T unwrap(Class<T> 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<Repository> rootRef,
Iterable<TriggerFactory> triggerFactories,
@@ -55,7 +55,7 @@ class LoggingRepository implements Repository, LogAccessCapability { mRepo = actual;
mLog = log;
- mStorages = new StorageCollection() {
+ mStoragePool = new StoragePool() {
protected <S extends Storable> Storage<S> createStorage(Class<S> type)
throws RepositoryException
{
@@ -71,7 +71,7 @@ class LoggingRepository implements Repository, LogAccessCapability { public <S extends Storable> Storage<S> storageFor(Class<S> 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<S extends Storable> extends WrappedStorage<S> { 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 <S extends Storable> Storage<S> createStorage(Class<S> type)
throws SupportException, RepositoryException
{
@@ -199,7 +199,7 @@ class ReplicatedRepository public <S extends Storable> Storage<S> storageFor(Class<S> 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<S extends Storable> implements Storage<S> { return mReplicaStorage.query(filter);
}
+ public void truncate() throws PersistException {
+ mMasterStorage.truncate();
+ mReplicaStorage.truncate();
+ }
+
public boolean addTrigger(Trigger<? super S> 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<Txn, S extends Storable> extends RawCursor<S> {
private static final byte[] NO_DATA = new byte[0];
- private final BDBTransactionManager<Txn> mTxnMgr;
+ private final TransactionManager<Txn> mTxnMgr;
private final BDBStorage<Txn, S> mStorage;
/**
* @param txnMgr
@@ -48,7 +50,7 @@ abstract class BDBCursor<Txn, S extends Storable> extends RawCursor<S> { * @throws ClassCastException if lock is not an object passed by
* {@link BDBStorage#openCursor BDBStorage.openCursor}
*/
- protected BDBCursor(BDBTransactionManager<Txn> txnMgr,
+ protected BDBCursor(TransactionManager<Txn> 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<Txn>
+abstract class BDBRepository<Txn> extends AbstractRepository<Txn>
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<TriggerFactory> mTriggerFactories;
private final AtomicReference<Repository> mRootRef;
private final StorableCodecFactory mStorableCodecFactory;
private final ExceptionTransformer mExTransformer;
- private final StorageCollection mStorages;
- private final Map<String, SequenceValueGenerator> mSequences;
- private final ThreadLocal<BDBTransactionManager<Txn>> mCurrentTxnMgr;
-
- private final Lock mShutdownLock;
- private final Condition mShutdownCondition;
- private int mShutdownBlockerCount;
-
- // Weakly tracks all BDBTransactionManager instances for shutdown hook.
- private final Map<BDBTransactionManager<Txn>, ?> 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<Txn> final File mEnvHome;
final String mSingleFileName;
- private final String mMergeSortTempDir;
-
private LayoutFactory mLayoutFactory;
private LobEngine mLobEngine;
@@ -146,40 +128,19 @@ abstract class BDBRepository<Txn> 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 <S extends Storable> Storage<S> createStorage(Class<S> type)
- throws RepositoryException
- {
- lockoutShutdown();
- try {
- try {
- return BDBRepository.this.createStorage(type);
- } catch (Exception e) {
- throw toRepositoryException(e);
- }
- } finally {
- unlockoutShutdown();
- }
- }
- };
-
- mSequences = new ConcurrentHashMap<String, SequenceValueGenerator>();
- mCurrentTxnMgr = new ThreadLocal<BDBTransactionManager<Txn>>();
- 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<Txn> mDataHome = builder.getDataHomeFile();
mEnvHome = builder.getEnvironmentHomeFile();
mSingleFileName = builder.getSingleFileName();
- // FIXME: see comments in builder
- mMergeSortTempDir = null; //builder.getMergeSortTempDirectory();
- }
-
- public String getName() {
- return mName;
- }
-
- public <S extends Storable> BDBStorage<Txn, S> storageFor(Class<S> type)
- throws MalformedTypeException, RepositoryException
- {
- return (BDBStorage<Txn, S>) 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 extends Capability> C getCapability(Class<C> 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<Txn> 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<Txn> public <S extends Storable> StorageAccess<S> storageAccessFor(Class<S> type)
throws RepositoryException
{
- return storageFor(type);
+ return (BDBStorage<Txn, S>) storageFor(type);
}
@Override
@@ -398,6 +273,78 @@ abstract class BDBRepository<Txn> 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 <S extends Storable> Storage createStorage(Class<S> 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<Txn> 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<Txn> 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<Txn> */
abstract void env_close() throws Exception;
- abstract <S extends Storable> BDBStorage<Txn, S> createStorage(Class<S> type)
+ abstract <S extends Storable> BDBStorage<Txn, S> createBDBStorage(Class<S> type)
throws Exception;
FetchException toFetchException(Throwable e) {
@@ -571,70 +484,11 @@ abstract class BDBRepository<Txn> }
/**
- * Returns the thread-local BDBTransactionManager instance, creating it if
- * needed.
- */
- BDBTransactionManager<Txn> openTransactionManager() {
- BDBTransactionManager<Txn> txnMgr = mCurrentTxnMgr.get();
- if (txnMgr == null) {
- lockoutShutdown();
- try {
- txnMgr = new BDBTransactionManager<Txn>(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<Txn> localTxnManager() {
+ return localTransactionManager();
}
/**
@@ -667,7 +521,8 @@ abstract class BDBRepository<Txn> * 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<BDBRepository>(repository);
mSleepInterval = sleepInterval;
@@ -800,7 +655,8 @@ abstract class BDBRepository<Txn> * @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<BDBRepository>(repository);
mSleepInterval = sleepInterval;
@@ -831,111 +687,4 @@ abstract class BDBRepository<Txn> }
}
}
-
- private static class ShutdownHook extends Thread {
- private final WeakReference<BDBRepository<?>> mRepository;
-
- ShutdownHook(BDBRepository repository) {
- super("BDBRepository shutdown (" + repository.getName() + ')');
- mRepository = new WeakReference<BDBRepository<?>>(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; * <li>{@link com.amazon.carbonado.capability.StorableInfoCapability StorableInfoCapability}
* <li>{@link com.amazon.carbonado.capability.ShutdownCapability ShutdownCapability}
* <li>{@link com.amazon.carbonado.layout.LayoutCapability LayoutCapability}
+ * <li>{@link com.amazon.carbonado.sequence.SequenceCapability SequenceCapability}
* <li>{@link CheckpointCapability CheckpointCapability}
* <li>{@link EnvironmentCapability EnvironmentCapability}
* </ul>
@@ -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;
@@ -283,29 +283,6 @@ public class BDBRepositoryBuilder extends AbstractRepositoryBuilder { }
/**
- * 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,
* unless data directories have been specified. For BDBRepositories that
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<Txn, S extends Storable> implements Storage<S>, Storag }
public S prepare() {
- return mStorableCodec.instantiate(mRawSupport);
+ return mStorableCodec.instantiate();
}
public Query<S> query() throws FetchException {
@@ -169,6 +171,48 @@ abstract class BDBStorage<Txn, S extends Storable> implements Storage<S>, 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<S> 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<Txn> 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<? super S> trigger) {
return mTriggerManager.addTrigger(trigger);
}
@@ -222,9 +266,6 @@ abstract class BDBStorage<Txn, S extends Storable> implements Storage<S>, 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<S>(mRootStorage);
}
@@ -262,7 +303,7 @@ abstract class BDBStorage<Txn, S extends Storable> implements Storage<S>, Storag boolean reverseOrder)
throws FetchException
{
- BDBTransactionManager<Txn> txnMgr = openTransactionManager();
+ TransactionManager<Txn> txnMgr = localTxnManager();
if (reverseRange) {
{
@@ -367,6 +408,12 @@ abstract class BDBStorage<Txn, S extends Storable> implements Storage<S>, 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<S> info = StorableIntrospector.examine(getStorableType());
@@ -386,11 +433,11 @@ abstract class BDBStorage<Txn, S extends Storable> implements Storage<S>, Storag boolean isPrimaryEmpty;
try {
- BDBTransactionManager<Txn> txnMgr = mRepository.openTransactionManager();
+ TransactionManager<Txn> 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<Txn, S extends Storable> implements Storage<S>, 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<Txn, S extends Storable> implements Storage<S>, Storag mQueryEngine = new QueryEngine<S>(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<S> compact() throws RepositoryException {
@@ -493,7 +543,7 @@ abstract class BDBStorage<Txn, S extends Storable> implements Storage<S>, 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<Txn, S extends Storable> implements Storage<S>, Storag * @param database database to use
*/
protected abstract BDBCursor<Txn, S> openCursor
- (BDBTransactionManager<Txn> txnMgr,
+ (TransactionManager<Txn> txnMgr,
byte[] startBound, boolean inclusiveStart,
byte[] endBound, boolean inclusiveEnd,
int maxPrefix,
@@ -588,8 +638,8 @@ abstract class BDBStorage<Txn, S extends Storable> implements Storage<S>, Storag return mRepository.toRepositoryException(e);
}
- BDBTransactionManager<Txn> openTransactionManager() {
- return mRepository.openTransactionManager();
+ TransactionManager<Txn> localTxnManager() {
+ return mRepository.localTxnManager();
}
/**
@@ -647,7 +697,7 @@ abstract class BDBStorage<Txn, S extends Storable> implements Storage<S>, Storag * prevent threads from starting work that will likely fail along the way.
*/
void checkClosed() throws FetchException {
- BDBTransactionManager<Txn> txnMgr = openTransactionManager();
+ TransactionManager<Txn> txnMgr = localTxnManager();
// Lock out shutdown task.
txnMgr.getLock().lock();
@@ -668,7 +718,7 @@ abstract class BDBStorage<Txn, S extends Storable> implements Storage<S>, Storag }
void close() throws Exception {
- BDBTransactionManager<Txn> txnMgr = mRepository.openTransactionManager();
+ TransactionManager<Txn> txnMgr = mRepository.localTxnManager();
txnMgr.getLock().lock();
try {
if (mPrimaryDatabase != null) {
@@ -811,7 +861,7 @@ abstract class BDBStorage<Txn, S extends Storable> implements Storage<S>, Storag }
public byte[] tryLoad(byte[] key) throws FetchException {
- BDBTransactionManager<Txn> txnMgr = mStorage.openTransactionManager();
+ TransactionManager<Txn> txnMgr = mStorage.localTxnManager();
byte[] result;
// Lock out shutdown task.
txnMgr.getLock().lock();
@@ -834,7 +884,7 @@ abstract class BDBStorage<Txn, S extends Storable> implements Storage<S>, Storag }
public boolean tryInsert(S storable, byte[] key, byte[] value) throws PersistException {
- BDBTransactionManager<Txn> txnMgr = mStorage.openTransactionManager();
+ TransactionManager<Txn> txnMgr = mStorage.localTxnManager();
Object result;
// Lock out shutdown task.
txnMgr.getLock().lock();
@@ -857,7 +907,7 @@ abstract class BDBStorage<Txn, S extends Storable> implements Storage<S>, Storag }
public void store(S storable, byte[] key, byte[] value) throws PersistException {
- BDBTransactionManager<Txn> txnMgr = mStorage.openTransactionManager();
+ TransactionManager<Txn> txnMgr = mStorage.localTxnManager();
// Lock out shutdown task.
txnMgr.getLock().lock();
try {
@@ -874,7 +924,7 @@ abstract class BDBStorage<Txn, S extends Storable> implements Storage<S>, Storag }
public boolean tryDelete(byte[] key) throws PersistException {
- BDBTransactionManager<Txn> txnMgr = mStorage.openTransactionManager();
+ TransactionManager<Txn> txnMgr = mStorage.localTxnManager();
// Lock out shutdown task.
txnMgr.getLock().lock();
try {
@@ -907,7 +957,11 @@ abstract class BDBStorage<Txn, S extends Storable> implements Storage<S>, 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<? super S> getInsertTrigger() {
diff --git a/src/main/java/com/amazon/carbonado/spi/AbstractSequenceValueProducer.java b/src/main/java/com/amazon/carbonado/sequence/AbstractSequenceValueProducer.java index 5badf0f..8deb3d7 100644 --- a/src/main/java/com/amazon/carbonado/spi/AbstractSequenceValueProducer.java +++ b/src/main/java/com/amazon/carbonado/sequence/AbstractSequenceValueProducer.java @@ -16,7 +16,7 @@ * limitations under the License.
*/
-package com.amazon.carbonado.spi;
+package com.amazon.carbonado.sequence;
import java.math.BigInteger;
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/spi/SequenceValueGenerator.java b/src/main/java/com/amazon/carbonado/sequence/SequenceValueGenerator.java index efe8ebd..1371a32 100644 --- a/src/main/java/com/amazon/carbonado/spi/SequenceValueGenerator.java +++ b/src/main/java/com/amazon/carbonado/sequence/SequenceValueGenerator.java @@ -16,7 +16,7 @@ * limitations under the License.
*/
-package com.amazon.carbonado.spi;
+package com.amazon.carbonado.sequence;
import com.amazon.carbonado.FetchException;
import com.amazon.carbonado.PersistException;
@@ -29,13 +29,14 @@ 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 {
- private static final int DEFAULT_RESERVE_AMOUNT = 100;
- private static final int DEFAULT_INITIAL_VALUE = 1;
- private static final int DEFAULT_INCREMENT = 1;
+ 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<StoredSequence> mStorage;
@@ -107,10 +108,40 @@ public class SequenceValueGenerator extends AbstractSequenceValueProducer { 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);
- if (!mStoredSequence.tryInsert()) {
+
+ // 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();
}
}
diff --git a/src/main/java/com/amazon/carbonado/spi/SequenceValueProducer.java b/src/main/java/com/amazon/carbonado/sequence/SequenceValueProducer.java index 659de89..5f7fa03 100644 --- a/src/main/java/com/amazon/carbonado/spi/SequenceValueProducer.java +++ b/src/main/java/com/amazon/carbonado/sequence/SequenceValueProducer.java @@ -16,14 +16,16 @@ * limitations under the License.
*/
-package com.amazon.carbonado.spi;
+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 {
@@ -84,4 +86,18 @@ public interface SequenceValueProducer { * @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.
+ *
+ * <p>This method should be called during the shutdown process of a
+ * repository, although calling it does not invalidate this
+ * SequenceValueGenerator. If getNextValue is called again, it will reserve
+ * values again.
+ *
+ * @return true if reserved values were returned
+ */
+ public boolean returnReservedValues() throws FetchException, PersistException;
}
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<String, SequenceValueProducer, RepositoryException>
+{
+ 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:
+ *
+ * <pre>
+ * CREATE TABLE CARBONADO_SEQUENCE (
+ * NAME VARCHAR(100) PRIMARY KEY,
+ * INITIAL_VALUE BIGINT NOT NULL,
+ * NEXT_VALUE BIGINT NOT NULL
+ * )
+ * </pre>
+ *
+ * @author Brian S O'Neill
+ */
+@PrimaryKey("name")
+@Authoritative
+@Independent
+@Alias("CARBONADO_SEQUENCE")
+public interface StoredSequence extends Storable<StoredSequence> {
+ @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 <Txn> Transaction type
+ * @author Brian S O'Neill
+ */
+public abstract class AbstractRepository<Txn>
+ implements Repository, ShutdownCapability, SequenceCapability
+{
+ private final String mName;
+ private final ReadWriteLock mShutdownLock;
+
+ private final ThreadLocal<TransactionManager<Txn>> mCurrentTxnMgr;
+
+ // Weakly tracks all TransactionManager instances for shutdown hook.
+ private final Map<TransactionManager<Txn>, ?> mAllTxnMgrs;
+
+ private final StoragePool mStoragePool;
+
+ private final SequenceValueProducerPool mSequencePool;
+
+ private ShutdownHook mShutdownHook;
+ volatile boolean mHasShutdown;
+
+ protected AbstractRepository(String name) {
+ if (name == null) {
+ throw new IllegalArgumentException("Repository name cannot be null");
+ }
+ mName = name;
+ mShutdownLock = new ReentrantReadWriteLock();
+ mCurrentTxnMgr = new ThreadLocal<TransactionManager<Txn>>();
+ mAllTxnMgrs = new WeakIdentityMap();
+
+ mStoragePool = new StoragePool() {
+ protected <S extends Storable> Storage<S> createStorage(Class<S> type)
+ throws RepositoryException
+ {
+ AbstractRepository.this.lockoutShutdown();
+ try {
+ return AbstractRepository.this.createStorage(type);
+ } finally {
+ AbstractRepository.this.unlockoutShutdown();
+ }
+ }
+ };
+
+ mSequencePool = new SequenceValueProducerPool() {
+ protected SequenceValueProducer createSequenceValueProducer(String name)
+ throws RepositoryException
+ {
+ AbstractRepository.this.lockoutShutdown();
+ try {
+ return AbstractRepository.this.createSequenceValueProducer(name);
+ } finally {
+ AbstractRepository.this.unlockoutShutdown();
+ }
+ }
+ };
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public <S extends Storable> Storage<S> storageFor(Class<S> type)
+ throws SupportException, RepositoryException
+ {
+ return mStoragePool.get(type);
+ }
+
+ public Transaction enterTransaction() {
+ return localTransactionManager().enter(null);
+ }
+
+ public Transaction enterTransaction(IsolationLevel level) {
+ return localTransactionManager().enter(level);
+ }
+
+ public Transaction enterTopTransaction(IsolationLevel level) {
+ return localTransactionManager().enterTop(level);
+ }
+
+ public IsolationLevel getTransactionIsolationLevel() {
+ return localTransactionManager().getIsolationLevel();
+ }
+
+ /**
+ * Default implementation checks if Repository implements Capability
+ * interface, and if so, returns the Repository.
+ */
+ @SuppressWarnings("unchecked")
+ public <C extends Capability> C getCapability(Class<C> capabilityType) {
+ if (capabilityType.isInstance(this)) {
+ return (C) this;
+ }
+ return null;
+ }
+
+ public void close() {
+ shutdown(false);
+ }
+
+ // Required by ShutdownCapability.
+ public boolean isAutoShutdownEnabled() {
+ return mShutdownHook != null;
+ }
+
+ // Required by ShutdownCapability.
+ public void setAutoShutdownEnabled(boolean enabled) {
+ if (mShutdownHook == null) {
+ if (enabled) {
+ mShutdownHook = new ShutdownHook(this);
+ try {
+ Runtime.getRuntime().addShutdownHook(mShutdownHook);
+ } catch (IllegalStateException e) {
+ // Shutdown in progress, so immediately run hook.
+ mShutdownHook.run();
+ }
+ }
+ } else {
+ if (!enabled) {
+ try {
+ Runtime.getRuntime().removeShutdownHook(mShutdownHook);
+ } catch (IllegalStateException e) {
+ // Shutdown in progress, hook is running.
+ }
+ mShutdownHook = null;
+ }
+ }
+ }
+
+ // Required by ShutdownCapability.
+ public void shutdown() {
+ shutdown(true);
+ }
+
+ private void shutdown(boolean suspendThreads) {
+ if (!mHasShutdown) {
+ // Since this repository is being closed before system shutdown,
+ // remove shutdown hook and run it now.
+ ShutdownHook hook = mShutdownHook;
+ if (hook != null) {
+ try {
+ Runtime.getRuntime().removeShutdownHook(hook);
+ } catch (IllegalStateException e) {
+ // Shutdown in progress, hook is running.
+ hook = null;
+ }
+ } else {
+ // If hook is null, auto-shutdown was disabled. Make a new
+ // instance to use, but don't register it.
+ hook = new ShutdownHook(this);
+ }
+ if (hook != null) {
+ hook.run(suspendThreads);
+ }
+ mHasShutdown = true;
+ }
+ }
+
+ // Required by SequenceCapability.
+ public SequenceValueProducer getSequenceValueProducer(String name) throws RepositoryException {
+ return mSequencePool.get(name);
+ }
+
+ /**
+ * Returns the thread-local TransactionManager, creating it if needed.
+ */
+ protected TransactionManager<Txn> localTransactionManager() {
+ TransactionManager<Txn> txnMgr = mCurrentTxnMgr.get();
+ if (txnMgr == null) {
+ lockoutShutdown();
+ try {
+ txnMgr = createTransactionManager();
+ mCurrentTxnMgr.set(txnMgr);
+ mAllTxnMgrs.put(txnMgr, null);
+ } finally {
+ unlockoutShutdown();
+ }
+ }
+ return txnMgr;
+ }
+
+ /**
+ * Call to prevent shutdown hook from running. Be sure to call
+ * unlockoutShutdown afterwards.
+ */
+ protected void lockoutShutdown() {
+ mShutdownLock.readLock().lock();
+ }
+
+ /**
+ * Only call this to release lockoutShutdown.
+ */
+ protected void unlockoutShutdown() {
+ mShutdownLock.readLock().unlock();
+ }
+
+ /**
+ * Only to be called by shutdown hook itself.
+ */
+ void lockForShutdown() {
+ mShutdownLock.writeLock().lock();
+ }
+
+ /**
+ * Only to be called by shutdown hook itself.
+ */
+ void unlockForShutdown() {
+ mShutdownLock.writeLock().unlock();
+ }
+
+ /**
+ * Returns all available Storage instances.
+ */
+ protected Collection<Storage> allStorage() {
+ return mStoragePool.values();
+ }
+
+ /**
+ * Install custom shutdown logic by overriding this method. By default it
+ * does nothing.
+ */
+ protected void shutdownHook() {
+ }
+
+ /**
+ * Return the main Log object for this Repository. If none provided, then
+ * no messages are logged by AbstractRepository.
+ */
+ protected abstract Log getLog();
+
+ /**
+ * Called upon to create a new thread-local TransactionManager instance.
+ */
+ protected abstract TransactionManager<Txn> createTransactionManager();
+
+ /**
+ * Called upon to create a new Storage instance.
+ */
+ protected abstract <S extends Storable> Storage<S> createStorage(Class<S> type)
+ throws RepositoryException;
+
+ /**
+ * Called upon to create a new SequenceValueProducer instance.
+ */
+ protected abstract SequenceValueProducer createSequenceValueProducer(String name)
+ throws RepositoryException;
+
+ void info(String message) {
+ Log log = getLog();
+ if (log != null) {
+ log.info(message);
+ }
+ }
+
+ void error(String message, Throwable e) {
+ Log log = getLog();
+ if (log != null) {
+ log.error(message, e);
+ }
+ }
+
+ private static class ShutdownHook extends Thread {
+ private final WeakReference<AbstractRepository<?>> mRepository;
+
+ ShutdownHook(AbstractRepository repository) {
+ super(repository.getClass().getSimpleName() + " shutdown (" +
+ repository.getName() + ')');
+ mRepository = new WeakReference<AbstractRepository<?>>(repository);
+ }
+
+ public void run() {
+ run(true);
+ }
+
+ public void run(boolean suspendThreads) {
+ AbstractRepository<?> repository = mRepository.get();
+ if (repository == null) {
+ return;
+ }
+
+ repository.info("Closing repository \"" + repository.getName() + '"');
+
+ try {
+ doShutdown(repository, suspendThreads);
+ } finally {
+ repository.mHasShutdown = true;
+ mRepository.clear();
+ repository.info("Finished closing repository \"" + repository.getName() + '"');
+ }
+ }
+
+ private void doShutdown(AbstractRepository<?> repository, boolean suspendThreads) {
+ repository.lockForShutdown();
+ try {
+ // Return unused sequence values.
+ repository.mSequencePool.returnReservedValues(null);
+
+ // Close transactions and cursors.
+ for (TransactionManager<?> txnMgr : repository.mAllTxnMgrs.keySet()) {
+ if (suspendThreads) {
+ // Lock transaction manager but don't release it. This
+ // prevents other threads from beginning work during
+ // shutdown, which will likely fail along the way.
+ txnMgr.getLock().lock();
+ }
+ try {
+ txnMgr.close();
+ } catch (Throwable e) {
+ repository.error("Failed to close TransactionManager", e);
+ }
+ }
+
+ repository.shutdownHook();
+ } finally {
+ repository.unlockForShutdown();
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/spi/BelatedStorageCreator.java b/src/main/java/com/amazon/carbonado/spi/BelatedStorageCreator.java index ed37df0..e0ea552 100644 --- a/src/main/java/com/amazon/carbonado/spi/BelatedStorageCreator.java +++ b/src/main/java/com/amazon/carbonado/spi/BelatedStorageCreator.java @@ -120,6 +120,10 @@ public class BelatedStorageCreator<S extends Storable> return mRepo;
}
+ public void truncate() {
+ throw error();
+ }
+
public boolean addTrigger(Trigger<? super S> trigger) {
throw error();
}
diff --git a/src/main/java/com/amazon/carbonado/spi/CodeBuilderUtil.java b/src/main/java/com/amazon/carbonado/spi/CodeBuilderUtil.java index e3b24bd..0623f94 100644 --- a/src/main/java/com/amazon/carbonado/spi/CodeBuilderUtil.java +++ b/src/main/java/com/amazon/carbonado/spi/CodeBuilderUtil.java @@ -21,7 +21,9 @@ import java.util.HashSet; import java.util.Set;
import java.util.Map;
import java.util.HashMap;
+
import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
import org.cojen.classfile.ClassFile;
import org.cojen.classfile.Modifiers;
@@ -31,9 +33,11 @@ import org.cojen.classfile.Label; import org.cojen.classfile.TypeDesc;
import org.cojen.classfile.LocalVariable;
import org.cojen.classfile.MethodDesc;
+import org.cojen.classfile.Opcode;
import org.cojen.util.ClassInjector;
import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.SupportException;
import static com.amazon.carbonado.spi.CommonMethodNames.*;
@@ -133,7 +137,7 @@ public class CodeBuilderUtil { public static void defineCopyBridges(ClassFile cf, Class leaf) {
for (Class c : gatherAllBridgeTypes(new HashSet<Class>(), leaf)) {
if (c != Object.class) {
- defineCopyBridge(cf, c);
+ defineCopyBridge(cf, leaf, c);
}
}
}
@@ -144,11 +148,17 @@ public class CodeBuilderUtil { * return it as the correct type.
*
* @param cf file to which to add the copy bridge
+ * @param leaf leaf class
* @param returnClass type returned from generated bridge method
*/
- public static void defineCopyBridge(ClassFile cf, Class returnClass) {
+ private static void defineCopyBridge(ClassFile cf, Class leaf, Class returnClass) {
TypeDesc returnType = TypeDesc.forClass(returnClass);
+ if (isPublicMethodFinal(leaf, COPY_METHOD_NAME, returnType, null)) {
+ // Cannot override.
+ return;
+ }
+
MethodInfo mi = cf.addMethod(Modifiers.PUBLIC.toBridge(true),
COPY_METHOD_NAME, returnType, null);
CodeBuilder b = new CodeBuilder(mi);
@@ -186,6 +196,38 @@ public class CodeBuilderUtil { }
/**
+ * Returns true if a public final method exists which matches the given
+ * specification.
+ */
+ public static boolean isPublicMethodFinal(Class clazz, String name,
+ TypeDesc retType, TypeDesc[] params)
+ {
+ if (!clazz.isInterface()) {
+ Class[] paramClasses;
+ if (params == null || params.length == 0) {
+ paramClasses = null;
+ } else {
+ paramClasses = new Class[params.length];
+ for (int i=0; i<params.length; i++) {
+ paramClasses[i] = params[i].toClass();
+ }
+ }
+ try {
+ Method existing = clazz.getMethod(name, paramClasses);
+ if (Modifier.isFinal(existing.getModifiers())) {
+ if (TypeDesc.forClass(existing.getReturnType()) == retType) {
+ // Method is already implemented and is final.
+ return true;
+ }
+ }
+ } catch (NoSuchMethodException e) {
+ }
+ }
+
+ return false;
+ }
+
+ /**
* Define a classfile appropriate for most Storables. Specifically:
* <ul>
* <li>implements Storable</li>
@@ -382,6 +424,78 @@ public class CodeBuilderUtil { }
/**
+ * Generates code to push an initial version property value on the stack.
+ *
+ * @throws SupportException if version type is not supported
+ */
+ public static void initialVersion(CodeBuilder b, TypeDesc type, int value)
+ throws SupportException
+ {
+ adjustVersion(b, type, value, false);
+ }
+
+ /**
+ * Generates code to increment a version property value, already on the stack.
+ *
+ * @throws SupportException if version type is not supported
+ */
+ public static void incrementVersion(CodeBuilder b, TypeDesc type)
+ throws SupportException
+ {
+ adjustVersion(b, type, 0, true);
+ }
+
+ private static void adjustVersion(CodeBuilder b, TypeDesc type, int value, boolean increment)
+ throws SupportException
+ {
+ TypeDesc primitiveType = type.toPrimitiveType();
+ supportCheck: {
+ if (primitiveType != null) {
+ switch (primitiveType.getTypeCode()) {
+ case TypeDesc.INT_CODE:
+ case TypeDesc.LONG_CODE:
+ break supportCheck;
+ }
+ }
+ throw new SupportException("Unsupported version type: " + type.getFullName());
+ }
+
+ if (!increment) {
+ if (primitiveType == TypeDesc.LONG) {
+ b.loadConstant((long) value);
+ } else {
+ b.loadConstant(value);
+ }
+ } else {
+ Label setVersion = b.createLabel();
+ if (!type.isPrimitive()) {
+ b.dup();
+ Label versionNotNull = b.createLabel();
+ b.ifNullBranch(versionNotNull, false);
+ b.pop();
+ if (primitiveType == TypeDesc.LONG) {
+ b.loadConstant(1L);
+ } else {
+ b.loadConstant(1);
+ }
+ b.branch(setVersion);
+ versionNotNull.setLocation();
+ b.convert(type, primitiveType);
+ }
+ if (primitiveType == TypeDesc.LONG) {
+ b.loadConstant(1L);
+ b.math(Opcode.LADD);
+ } else {
+ b.loadConstant(1);
+ b.math(Opcode.IADD);
+ }
+ setVersion.setLocation();
+ }
+
+ b.convert(primitiveType, type);
+ }
+
+ /**
* Determines which overloaded "with" method on Query should be bound to.
*/
public static TypeDesc bindQueryParam(Class clazz) {
@@ -403,4 +517,58 @@ public class CodeBuilderUtil { }
return TypeDesc.OBJECT;
}
+
+ /**
+ * Appends a String to a StringBuilder. A StringBuilder and String must be
+ * on the stack, and a StringBuilder is left on the stack after the call.
+ */
+ public static void callStringBuilderAppendString(CodeBuilder b) {
+ // Because of JDK1.5 bug which exposes AbstractStringBuilder class,
+ // cannot use reflection to get method signature.
+ TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class);
+ b.invokeVirtual(stringBuilder, "append", stringBuilder, new TypeDesc[] {TypeDesc.STRING});
+ }
+
+ /**
+ * Appends a char to a StringBuilder. A StringBuilder and char must be on
+ * the stack, and a StringBuilder is left on the stack after the call.
+ */
+ public static void callStringBuilderAppendChar(CodeBuilder b) {
+ // Because of JDK1.5 bug which exposes AbstractStringBuilder class,
+ // cannot use reflection to get method signature.
+ TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class);
+ b.invokeVirtual(stringBuilder, "append", stringBuilder, new TypeDesc[] {TypeDesc.CHAR});
+ }
+
+ /**
+ * Calls length on a StringBuilder on the stack, leaving an int on the stack.
+ */
+ public static void callStringBuilderLength(CodeBuilder b) {
+ // Because of JDK1.5 bug which exposes AbstractStringBuilder class,
+ // cannot use reflection to get method signature.
+ TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class);
+ b.invokeVirtual(stringBuilder, "length", TypeDesc.INT, null);
+ }
+
+ /**
+ * Calls setLength on a StringBuilder. A StringBuilder and int must be on
+ * the stack, and both are consumed after the call.
+ */
+ public static void callStringBuilderSetLength(CodeBuilder b) {
+ // Because of JDK1.5 bug which exposes AbstractStringBuilder class,
+ // cannot use reflection to get method signature.
+ TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class);
+ b.invokeVirtual(stringBuilder, "setLength", null, new TypeDesc[] {TypeDesc.INT});
+ }
+
+ /**
+ * Calls toString on a StringBuilder. A StringBuilder must be on the stack,
+ * and a String is left on the stack after the call.
+ */
+ public static void callStringBuilderToString(CodeBuilder b) {
+ // Because of JDK1.5 bug which exposes AbstractStringBuilder class,
+ // cannot use reflection to get method signature.
+ TypeDesc stringBuilder = TypeDesc.forClass(StringBuilder.class);
+ b.invokeVirtual(stringBuilder, "toString", TypeDesc.STRING, null);
+ }
}
diff --git a/src/main/java/com/amazon/carbonado/spi/LobEngine.java b/src/main/java/com/amazon/carbonado/spi/LobEngine.java index a897cac..d91ce5e 100644 --- a/src/main/java/com/amazon/carbonado/spi/LobEngine.java +++ b/src/main/java/com/amazon/carbonado/spi/LobEngine.java @@ -55,6 +55,9 @@ import com.amazon.carbonado.lob.BlobClob; import com.amazon.carbonado.lob.Clob;
import com.amazon.carbonado.lob.Lob;
+import com.amazon.carbonado.sequence.SequenceValueGenerator;
+import com.amazon.carbonado.sequence.SequenceValueProducer;
+
/**
* Complete Lob support for repositories, although repository is responsible
* for binding Lob properties to this engine. Lobs are referenced by locators,
@@ -83,16 +86,32 @@ public class LobEngine { final Repository mRepo;
final Storage<StoredLob> mLobStorage;
final Storage<StoredLob.Block> mLobBlockStorage;
+ final SequenceValueProducer mLocatorSequence;
private Map mTriggers;
/**
- * @param repo storage for Lobs
+ * @param lobRepo storage for Lobs - should not be replicated
+ * @param locatorRepo storage for producing unique values for Lob locators
+ * - should be root repository
+ */
+ public LobEngine(Repository lobRepo, Repository locatorRepo) throws RepositoryException {
+ // Cannot reliably use sequences provided by Lob repository, since
+ // LobEngine is used internally by repositories.
+ this(lobRepo, new SequenceValueGenerator(locatorRepo, StoredLob.class.getName()));
+ }
+
+ /**
+ * @param lobRepo storage for Lobs - should not be replicated
+ * @param locatorSequenceProducer source of unique values for Lob locators
*/
- public LobEngine(Repository repo) throws RepositoryException {
- mRepo = repo;
- mLobStorage = repo.storageFor(StoredLob.class);
- mLobBlockStorage = repo.storageFor(StoredLob.Block.class);
+ public LobEngine(Repository lobRepo, SequenceValueProducer locatorSequenceProducer)
+ throws RepositoryException
+ {
+ mRepo = lobRepo;
+ mLobStorage = lobRepo.storageFor(StoredLob.class);
+ mLobBlockStorage = lobRepo.storageFor(StoredLob.Block.class);
+ mLocatorSequence = locatorSequenceProducer;
}
/**
@@ -103,6 +122,7 @@ public class LobEngine { */
public Blob createNewBlob(int blockSize) throws PersistException {
StoredLob lob = mLobStorage.prepare();
+ lob.setLocator(mLocatorSequence.nextLongValue());
lob.setBlockSize(blockSize);
lob.setLength(0);
lob.insert();
@@ -117,6 +137,7 @@ public class LobEngine { */
public Clob createNewClob(int blockSize) throws PersistException {
StoredLob lob = mLobStorage.prepare();
+ lob.setLocator(mLocatorSequence.nextLongValue());
lob.setBlockSize(blockSize);
lob.setLength(0);
lob.insert();
diff --git a/src/main/java/com/amazon/carbonado/spi/MasterStorableGenerator.java b/src/main/java/com/amazon/carbonado/spi/MasterStorableGenerator.java index ead6c3c..3de8fb5 100644 --- a/src/main/java/com/amazon/carbonado/spi/MasterStorableGenerator.java +++ b/src/main/java/com/amazon/carbonado/spi/MasterStorableGenerator.java @@ -47,6 +47,8 @@ import com.amazon.carbonado.info.StorableInfo; import com.amazon.carbonado.info.StorableIntrospector;
import com.amazon.carbonado.info.StorableProperty;
+import com.amazon.carbonado.sequence.SequenceValueProducer;
+
import static com.amazon.carbonado.spi.CommonMethodNames.*;
/**
@@ -409,7 +411,8 @@ public final class MasterStorableGenerator<S extends Storable> { ordinal++;
if (property.isJoin() || property.isPrimaryKeyMember()
- || property.isNullable())
+ || property.isNullable()
+ || property.isAutomatic() || property.isVersion())
{
continue;
}
@@ -808,63 +811,25 @@ public final class MasterStorableGenerator<S extends Storable> { int value)
throws SupportException
{
- StorableProperty<?> versionProperty = mInfo.getVersionProperty();
-
- TypeDesc versionType = TypeDesc.forClass(versionProperty.getType());
- TypeDesc versionPrimitiveType = versionType.toPrimitiveType();
- supportCheck: {
- if (versionPrimitiveType != null) {
- switch (versionPrimitiveType.getTypeCode()) {
- case TypeDesc.INT_CODE:
- case TypeDesc.LONG_CODE:
- break supportCheck;
- }
- }
- throw new SupportException
- ("Unsupported version type: " + versionType.getFullName());
- }
-
+ // Push storable to stack in preparation for calling set method below.
if (storableVar == null) {
b.loadThis();
} else {
b.loadLocal(storableVar);
}
+ StorableProperty<?> versionProperty = mInfo.getVersionProperty();
+ TypeDesc versionType = TypeDesc.forClass(versionProperty.getType());
+
if (value >= 0) {
- if (versionPrimitiveType == TypeDesc.LONG) {
- b.loadConstant((long) value);
- } else {
- b.loadConstant(value);
- }
+ CodeBuilderUtil.initialVersion(b, versionType, value);
} else {
+ // Load current property value.
b.dup();
b.invoke(versionProperty.getReadMethod());
- Label setVersion = b.createLabel();
- if (!versionType.isPrimitive()) {
- b.dup();
- Label versionNotNull = b.createLabel();
- b.ifNullBranch(versionNotNull, false);
- b.pop();
- if (versionPrimitiveType == TypeDesc.LONG) {
- b.loadConstant(1L);
- } else {
- b.loadConstant(1);
- }
- b.branch(setVersion);
- versionNotNull.setLocation();
- b.convert(versionType, versionPrimitiveType);
- }
- if (versionPrimitiveType == TypeDesc.LONG) {
- b.loadConstant(1L);
- b.math(Opcode.LADD);
- } else {
- b.loadConstant(1);
- b.math(Opcode.IADD);
- }
- setVersion.setLocation();
+ CodeBuilderUtil.incrementVersion(b, versionType);
}
- b.convert(versionPrimitiveType, versionType);
b.invoke(versionProperty.getWriteMethod());
}
}
diff --git a/src/main/java/com/amazon/carbonado/spi/MasterSupport.java b/src/main/java/com/amazon/carbonado/spi/MasterSupport.java index de1e521..dd48194 100644 --- a/src/main/java/com/amazon/carbonado/spi/MasterSupport.java +++ b/src/main/java/com/amazon/carbonado/spi/MasterSupport.java @@ -20,6 +20,7 @@ package com.amazon.carbonado.spi; import com.amazon.carbonado.PersistException;
import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.sequence.SequenceValueProducer;
/**
* Provides runtime support for Storable classes generated by {@link MasterStorableGenerator}.
diff --git a/src/main/java/com/amazon/carbonado/spi/StorableGenerator.java b/src/main/java/com/amazon/carbonado/spi/StorableGenerator.java index 93f9b38..0c2d50d 100644 --- a/src/main/java/com/amazon/carbonado/spi/StorableGenerator.java +++ b/src/main/java/com/amazon/carbonado/spi/StorableGenerator.java @@ -24,6 +24,7 @@ import java.lang.ref.SoftReference; import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.math.BigInteger;
@@ -583,8 +584,11 @@ public final class StorableGenerator<S extends Storable> { requireStateField = true;
} else if (mGenMode == GEN_ABSTRACT) {
// Only define regular property fields if abstract
- // class. Wrapped class doesn't reference them.
- mClassFile.addField(Modifiers.PROTECTED, name, type);
+ // class. Wrapped class doesn't reference them. Double
+ // words are volatile to prevent word tearing without
+ // explicit synchronization.
+ mClassFile.addField(Modifiers.PROTECTED.toVolatile(type.isDoubleWord()),
+ name, type);
requireStateField = true;
}
@@ -617,11 +621,9 @@ public final class StorableGenerator<S extends Storable> { }
}
- if (mGenMode == GEN_ABSTRACT && (type.isDoubleWord() || property.isJoin())) {
- // Even if read method just reads a field,
- // synchronization is needed if type is a double
- // word. Synchronization is also required for join
- // property accessors, as they may alter bit masks.
+ if (mGenMode == GEN_ABSTRACT && property.isJoin()) {
+ // Synchronization is required for join property
+ // accessors, as they may alter bit masks.
mi.setModifiers(mi.getModifiers().toSynchronized(true));
}
@@ -1061,12 +1063,6 @@ public final class StorableGenerator<S extends Storable> { (Modifiers.PROTECTED, readName, toType, null);
mi.markSynthetic();
- if (type.isDoubleWord()) {
- // Even if read method just reads a field,
- // synchronization is needed if type is a double word.
- mi.setModifiers(mi.getModifiers().toSynchronized(true));
- }
-
// Now add code that actually gets the property value and
// then invokes adapt method.
CodeBuilder b = new CodeBuilder(mi);
@@ -1130,9 +1126,14 @@ public final class StorableGenerator<S extends Storable> { // Add tryLoad method which delegates to abstract doTryLoad method.
addTryLoad: {
// Define the tryLoad method.
- MethodInfo mi = mClassFile.addMethod
+ MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT),
TRY_LOAD_METHOD_NAME, TypeDesc.BOOLEAN, null);
+
+ if (mi == null) {
+ break addTryLoad;
+ }
+
mi.addException(TypeDesc.forClass(FetchException.class));
if (mGenMode == GEN_WRAPPED) {
@@ -1269,9 +1270,14 @@ public final class StorableGenerator<S extends Storable> { // Add load method which calls tryLoad.
addLoad: {
// Define the load method.
- MethodInfo mi = mClassFile.addMethod
+ MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT),
LOAD_METHOD_NAME, null, null);
+
+ if (mi == null) {
+ break addLoad;
+ }
+
mi.addException(TypeDesc.forClass(FetchException.class));
if (mGenMode == GEN_WRAPPED) {
@@ -1370,8 +1376,13 @@ public final class StorableGenerator<S extends Storable> { // Add insert method which calls insert(forTry = false)
addInsert: {
- MethodInfo mi = mClassFile.addMethod
+ MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC, INSERT_METHOD_NAME, null, null);
+
+ if (mi == null) {
+ break addInsert;
+ }
+
mi.addException(TypeDesc.forClass(PersistException.class));
if (mGenMode == GEN_WRAPPED) {
@@ -1391,8 +1402,13 @@ public final class StorableGenerator<S extends Storable> { // Add tryInsert method which calls insert(forTry = true)
addTryInsert: {
- MethodInfo mi = mClassFile.addMethod
+ MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC, TRY_INSERT_METHOD_NAME, TypeDesc.BOOLEAN, null);
+
+ if (mi == null) {
+ break addTryInsert;
+ }
+
mi.addException(TypeDesc.forClass(PersistException.class));
if (mGenMode == GEN_WRAPPED) {
@@ -1534,8 +1550,13 @@ public final class StorableGenerator<S extends Storable> { // Add update method which calls update(forTry = false)
addUpdate: {
- MethodInfo mi = mClassFile.addMethod
+ MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC, UPDATE_METHOD_NAME, null, null);
+
+ if (mi == null) {
+ break addUpdate;
+ }
+
mi.addException(TypeDesc.forClass(PersistException.class));
if (mGenMode == GEN_WRAPPED) {
@@ -1555,8 +1576,13 @@ public final class StorableGenerator<S extends Storable> { // Add tryUpdate method which calls update(forTry = true)
addTryUpdate: {
- MethodInfo mi = mClassFile.addMethod
+ MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC, TRY_UPDATE_METHOD_NAME, TypeDesc.BOOLEAN, null);
+
+ if (mi == null) {
+ break addTryUpdate;
+ }
+
mi.addException(TypeDesc.forClass(PersistException.class));
if (mGenMode == GEN_WRAPPED) {
@@ -1642,8 +1668,13 @@ public final class StorableGenerator<S extends Storable> { // Add delete method which calls delete(forTry = false)
addDelete: {
// Define the delete method.
- MethodInfo mi = mClassFile.addMethod
+ MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC, DELETE_METHOD_NAME, null, null);
+
+ if (mi == null) {
+ break addDelete;
+ }
+
mi.addException(TypeDesc.forClass(PersistException.class));
if (mGenMode == GEN_WRAPPED) {
@@ -1664,8 +1695,13 @@ public final class StorableGenerator<S extends Storable> { // Add tryDelete method which calls delete(forTry = true)
addTryDelete: {
// Define the delete method.
- MethodInfo mi = mClassFile.addMethod
+ MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC, TRY_DELETE_METHOD_NAME, TypeDesc.BOOLEAN, null);
+
+ if (mi == null) {
+ break addTryDelete;
+ }
+
mi.addException(TypeDesc.forClass(PersistException.class));
if (mGenMode == GEN_WRAPPED) {
@@ -1683,24 +1719,34 @@ public final class StorableGenerator<S extends Storable> { }
// Add storableType method
- {
+ addStorableType: {
final TypeDesc type = TypeDesc.forClass(mStorableType);
final TypeDesc storableClassType = TypeDesc.forClass(Class.class);
- MethodInfo mi = mClassFile.addMethod
+ MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC, STORABLE_TYPE_METHOD_NAME, storableClassType, null);
+
+ if (mi == null) {
+ break addStorableType;
+ }
+
CodeBuilder b = new CodeBuilder(mi);
b.loadConstant(type);
b.returnValue(storableClassType);
}
- // Add copy methods.
- {
+ // Add copy method.
+ addCopy: {
TypeDesc type = TypeDesc.forClass(mInfo.getStorableType());
// Add copy method.
- MethodInfo mi = mClassFile.addMethod
+ MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT),
COPY_METHOD_NAME, mClassFile.getType(), null);
+
+ if (mi == null) {
+ break addCopy;
+ }
+
CodeBuilder b = new CodeBuilder(mi);
b.loadThis();
b.invokeVirtual(CLONE_METHOD_NAME, TypeDesc.OBJECT, null);
@@ -1739,10 +1785,12 @@ public final class StorableGenerator<S extends Storable> { }
b.returnValue(type);
-
- CodeBuilderUtil.defineCopyBridges(mClassFile, mInfo.getStorableType());
}
+ // Part of properly defining copy method, except needs to be added even
+ // if copy method was not added because it is inherited and final.
+ CodeBuilderUtil.defineCopyBridges(mClassFile, mInfo.getStorableType());
+
// Create all the property copier methods.
// Boolean params: pkProperties, versionProperty, dataProperties, unequalOnly, dirtyOnly
addCopyPropertiesMethod(COPY_ALL_PROPERTIES,
@@ -1758,9 +1806,13 @@ public final class StorableGenerator<S extends Storable> { // Define hasDirtyProperties method.
addHasDirtyProps: {
- MethodInfo mi = mClassFile.addMethod
+ MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC, HAS_DIRTY_PROPERTIES, TypeDesc.BOOLEAN, null);
+ if (mi == null) {
+ break addHasDirtyProps;
+ }
+
if (mGenMode == GEN_WRAPPED) {
callWrappedStorable(mi);
break addHasDirtyProps;
@@ -1783,10 +1835,15 @@ public final class StorableGenerator<S extends Storable> { addPropertyStateCheckMethod(IS_PROPERTY_CLEAN, PROPERTY_STATE_CLEAN);
// Define isPropertySupported method.
- {
- MethodInfo mi = mClassFile.addMethod
+ addIsPropertySupported: {
+ MethodInfo mi = addMethodIfNotFinal
(Modifiers.PUBLIC, IS_PROPERTY_SUPPORTED,
TypeDesc.BOOLEAN, new TypeDesc[] {TypeDesc.STRING});
+
+ if (mi == null) {
+ break addIsPropertySupported;
+ }
+
CodeBuilder b = new CodeBuilder(mi);
b.loadThis();
@@ -2027,12 +2084,16 @@ public final class StorableGenerator<S extends Storable> { TypeDesc[] param = { TypeDesc.forClass(Storable.class) };
TypeDesc storableTypeDesc = TypeDesc.forClass(mStorableType);
- MethodInfo mi= mClassFile.addMethod
+ MethodInfo mi= addMethodIfNotFinal
(Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT),
methodName,
null,
param);
+ if (mi == null) {
+ return;
+ }
+
if (mGenMode == GEN_WRAPPED) {
callWrappedStorable(mi);
return;
@@ -2298,7 +2359,13 @@ public final class StorableGenerator<S extends Storable> { }
private void addMarkCleanMethod(String name) {
- MethodInfo mi = mClassFile.addMethod(Modifiers.PUBLIC, name, null, null);
+ MethodInfo mi =
+ addMethodIfNotFinal (Modifiers.PUBLIC.toSynchronized(true), name, null, null);
+
+ if (mi == null) {
+ return;
+ }
+
CodeBuilder b = new CodeBuilder(mi);
if (mGenMode == GEN_WRAPPED) {
@@ -2353,7 +2420,13 @@ public final class StorableGenerator<S extends Storable> { }
private void addMarkDirtyMethod(String name) {
- MethodInfo mi = mClassFile.addMethod(Modifiers.PUBLIC, name, null, null);
+ MethodInfo mi =
+ addMethodIfNotFinal(Modifiers.PUBLIC.toSynchronized(true), name, null, null);
+
+ if (mi == null) {
+ return;
+ }
+
CodeBuilder b = new CodeBuilder(mi);
if (mGenMode == GEN_WRAPPED) {
@@ -2506,6 +2579,22 @@ public final class StorableGenerator<S extends Storable> { private void addIsInitializedMethod
(String name, Map<String, ? extends StorableProperty<S>> properties)
{
+ // Don't check Automatic properties.
+ {
+ boolean cloned = false;
+ for (StorableProperty<S> prop : properties.values()) {
+ if (prop.isAutomatic() || prop.isVersion()) {
+ if (!cloned) {
+ properties = new LinkedHashMap<String, StorableProperty<S>>(properties);
+ cloned = true;
+ }
+ // This isn't concurrent modification since the loop is
+ // still operating on the original properties map.
+ properties.remove(prop.getName());
+ }
+ }
+ }
+
MethodInfo mi = mClassFile.addMethod(Modifiers.PROTECTED, name, TypeDesc.BOOLEAN, null);
CodeBuilder b = new CodeBuilder(mi);
@@ -2624,7 +2713,7 @@ public final class StorableGenerator<S extends Storable> { CodeBuilder b = new CodeBuilder(mi);
// Generate big switch statement that operates on Strings. See also
- // cojen.util.BeanPropertyAccessor, which also generates this kind of
+ // org.cojen.util.BeanPropertyAccessor, which also generates this kind of
// switch.
// For switch case count, obtain a prime number, at least twice as
@@ -2792,8 +2881,13 @@ public final class StorableGenerator<S extends Storable> { * @param state property state to check
*/
private void addPropertyStateCheckMethod(String name, int state) {
- MethodInfo mi = mClassFile.addMethod(Modifiers.PUBLIC, name,
- TypeDesc.BOOLEAN, new TypeDesc[] {TypeDesc.STRING});
+ MethodInfo mi = addMethodIfNotFinal(Modifiers.PUBLIC, name,
+ TypeDesc.BOOLEAN, new TypeDesc[] {TypeDesc.STRING});
+
+ if (mi == null) {
+ return;
+ }
+
CodeBuilder b = new CodeBuilder(mi);
if (mGenMode == GEN_WRAPPED) {
@@ -2825,7 +2919,11 @@ public final class StorableGenerator<S extends Storable> { */
private void addHashCodeMethod() {
Modifiers modifiers = Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT);
- MethodInfo mi = mClassFile.addMethod(modifiers, "hashCode", TypeDesc.INT, null);
+ MethodInfo mi = addMethodIfNotFinal(modifiers, "hashCode", TypeDesc.INT, null);
+
+ if (mi == null) {
+ return;
+ }
if (mGenMode == GEN_WRAPPED) {
callWrappedStorable(mi);
@@ -2968,9 +3066,13 @@ public final class StorableGenerator<S extends Storable> { }
Modifiers modifiers = Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT);
- MethodInfo mi = mClassFile.addMethod
+ MethodInfo mi = addMethodIfNotFinal
(modifiers, equalsMethodName, TypeDesc.BOOLEAN, objectParam);
+ if (mi == null) {
+ return;
+ }
+
if (mGenMode == GEN_WRAPPED && equalityType != EQUAL_FULL) {
callWrappedStorable(mi);
return;
@@ -3051,11 +3153,15 @@ public final class StorableGenerator<S extends Storable> { TypeDesc[] charParam = {TypeDesc.CHAR};
Modifiers modifiers = Modifiers.PUBLIC.toSynchronized(mGenMode == GEN_ABSTRACT);
- MethodInfo mi = mClassFile.addMethod(modifiers,
- keyOnly ?
- TO_STRING_KEY_ONLY_METHOD_NAME :
- TO_STRING_METHOD_NAME,
- TypeDesc.STRING, null);
+ MethodInfo mi = addMethodIfNotFinal(modifiers,
+ keyOnly ?
+ TO_STRING_KEY_ONLY_METHOD_NAME :
+ TO_STRING_METHOD_NAME,
+ TypeDesc.STRING, null);
+
+ if (mi == null) {
+ return;
+ }
if (mGenMode == GEN_WRAPPED) {
callWrappedStorable(mi);
@@ -3548,4 +3654,18 @@ public final class StorableGenerator<S extends Storable> { new TypeDesc[] {threadType, TypeDesc.forClass(Throwable.class)});
b.returnVoid();
}
+
+ /**
+ * @return MethodInfo for completing definition or null if superclass
+ * already implements method as final.
+ */
+ private MethodInfo addMethodIfNotFinal(Modifiers modifiers, String name,
+ TypeDesc retType, TypeDesc[] params)
+ {
+ if (CodeBuilderUtil.isPublicMethodFinal(mStorableType, name, retType, params)) {
+ return null;
+ }
+
+ return mClassFile.addMethod(modifiers, name, retType, params);
+ }
}
diff --git a/src/main/java/com/amazon/carbonado/spi/StorableIndexSet.java b/src/main/java/com/amazon/carbonado/spi/StorableIndexSet.java index 965880e..2f943ea 100644 --- a/src/main/java/com/amazon/carbonado/spi/StorableIndexSet.java +++ b/src/main/java/com/amazon/carbonado/spi/StorableIndexSet.java @@ -201,6 +201,26 @@ public class StorableIndexSet<S extends Storable> extends TreeSet<StorableIndex< }
/**
+ * Marks all indexes as clustered or non-clustered.
+ *
+ * @param clustered true to mark clustered; false to mark non-clustered
+ * @see StorableIndex#isClustered()
+ */
+ public void markClustered(boolean clustered) {
+ Map<StorableIndex<S>, StorableIndex<S>> replacements = null;
+ for (StorableIndex<S> index : this) {
+ StorableIndex<S> replacement = index.clustered(clustered);
+ if (replacement != index) {
+ if (replacements == null) {
+ replacements = new HashMap<StorableIndex<S>, StorableIndex<S>>();
+ }
+ replacements.put(index, replacement);
+ }
+ }
+ replaceEntries(replacements);
+ }
+
+ /**
* Augment non-unique indexes with primary key properties, thus making them
* unique.
*
diff --git a/src/main/java/com/amazon/carbonado/spi/StorageCollection.java b/src/main/java/com/amazon/carbonado/spi/StorageCollection.java deleted file mode 100644 index c75f350..0000000 --- a/src/main/java/com/amazon/carbonado/spi/StorageCollection.java +++ /dev/null @@ -1,128 +0,0 @@ -/*
- * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
- * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
- * of Amazon Technologies, Inc. or its affiliates. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.amazon.carbonado.spi;
-
-import java.util.IdentityHashMap;
-
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.ConcurrentHashMap;
-
-import com.amazon.carbonado.MalformedTypeException;
-import com.amazon.carbonado.RepositoryException;
-import com.amazon.carbonado.Storable;
-import com.amazon.carbonado.Storage;
-import com.amazon.carbonado.SupportException;
-
-import com.amazon.carbonado.info.StorableIntrospector;
-
-/**
- * Thread-safe container of Storage instances which creates Storage
- * on-demand. If multiple threads are requesting the same Storage concurrently,
- * only one thread actually creates the Storage. The other waits for it to
- * become available.
- *
- * @author Brian S O'Neill
- */
-public abstract class StorageCollection {
- private final ConcurrentMap<Class<?>, Storage> mStorageMap;
- private final ConcurrentMap<Class<?>, Object> mStorableTypeLockMap;
-
- public StorageCollection() {
- mStorageMap = new ConcurrentHashMap<Class<?>, Storage>();
- mStorableTypeLockMap = new ConcurrentHashMap<Class<?>, Object>();
- }
-
- public <S extends Storable> Storage<S> storageFor(Class<S> type)
- throws MalformedTypeException, SupportException, RepositoryException
- {
- Storage storage = mStorageMap.get(type);
- if (storage != null) {
- return storage;
- }
-
- getLock: while (true) {
- Object lock;
- boolean doCreate;
-
- {
- Object newLock = new Object();
- Object existingLock = mStorableTypeLockMap.putIfAbsent(type, newLock);
-
- if (existingLock == null) {
- lock = newLock;
- doCreate = true;
- } else {
- lock = existingLock;
- doCreate = false;
- }
- }
-
- if (Thread.holdsLock(lock)) {
- throw new IllegalStateException
- ("Recursively trying to create storage for type: " + type);
- }
-
- synchronized (lock) {
- try {
- // Check storage map again before creating new storage.
- checkLock: while (true) {
- storage = mStorageMap.get(type);
- if (storage != null) {
- return storage;
- }
- if (mStorableTypeLockMap.get(type) != lock) {
- // Lock has changed, so get a new lock.
- continue getLock;
- }
- if (doCreate) {
- break checkLock;
- }
- try {
- // Wait with a timeout to handle rare race condition.
- lock.wait(100);
- } catch (InterruptedException e) {
- throw new RepositoryException("Interrupted");
- }
- }
-
- // Examine and throw exception early if there is a problem.
- StorableIntrospector.examine(type);
-
- storage = createStorage(type);
-
- mStorageMap.put(type, storage);
- break getLock;
- } finally {
- // Storable type lock no longer needed.
- mStorableTypeLockMap.remove(type, lock);
- lock.notifyAll();
- }
- }
- }
-
- return storage;
- }
-
- public Iterable<Storage> allStorage() {
- return mStorageMap.values();
- }
-
- protected abstract <S extends Storable> Storage<S> createStorage(Class<S> type)
- throws SupportException, RepositoryException;
-}
diff --git a/src/main/java/com/amazon/carbonado/spi/StoragePool.java b/src/main/java/com/amazon/carbonado/spi/StoragePool.java new file mode 100644 index 0000000..9382723 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/spi/StoragePool.java @@ -0,0 +1,59 @@ +/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.spi;
+
+import com.amazon.carbonado.MalformedTypeException;
+import com.amazon.carbonado.RepositoryException;
+import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.Storage;
+import com.amazon.carbonado.SupportException;
+
+import com.amazon.carbonado.util.AbstractPool;
+
+/**
+ * A concurrent pool of strongly referenced Storage instances mapped by
+ * Storable type. Storage instances are lazily created and pooled.
+ *
+ * @author Brian S O'Neill
+ */
+public abstract class StoragePool
+ extends AbstractPool<Class, Storage, RepositoryException>
+{
+ public StoragePool() {
+ }
+
+ /**
+ * Returns a Storage instance for the given Storable type, which is lazily
+ * created and pooled. If multiple threads are requesting upon the same type
+ * concurrently, at most one thread attempts to lazily create the
+ * Storage. The others wait for it to become available.
+ */
+ public <S extends Storable> Storage<S> getStorage(Class<S> type)
+ throws MalformedTypeException, SupportException, RepositoryException
+ {
+ return (Storage<S>) super.get(type);
+ }
+
+ protected final Storage create(Class type) throws SupportException, RepositoryException {
+ return createStorage(type);
+ }
+
+ protected abstract <S extends Storable> Storage<S> createStorage(Class<S> type)
+ throws SupportException, RepositoryException;
+}
diff --git a/src/main/java/com/amazon/carbonado/spi/StoredLob.java b/src/main/java/com/amazon/carbonado/spi/StoredLob.java index 2435cb1..e409e60 100644 --- a/src/main/java/com/amazon/carbonado/spi/StoredLob.java +++ b/src/main/java/com/amazon/carbonado/spi/StoredLob.java @@ -19,10 +19,8 @@ package com.amazon.carbonado.spi;
import com.amazon.carbonado.Alias;
-import com.amazon.carbonado.FetchException;
import com.amazon.carbonado.Independent;
import com.amazon.carbonado.PrimaryKey;
-import com.amazon.carbonado.Sequence;
import com.amazon.carbonado.Storable;
import com.amazon.carbonado.Version;
@@ -38,7 +36,6 @@ import com.amazon.carbonado.constraint.IntegerConstraint; @Independent
@Alias("CARBONADO_LOB")
public abstract class StoredLob implements Storable<StoredLob> {
- @Sequence("com.amazon.carbonado.spi.StoredLob")
public abstract long getLocator();
public abstract void setLocator(long locator);
diff --git a/src/main/java/com/amazon/carbonado/spi/WrappedStorage.java b/src/main/java/com/amazon/carbonado/spi/WrappedStorage.java index 17db288..71f6fbc 100644 --- a/src/main/java/com/amazon/carbonado/spi/WrappedStorage.java +++ b/src/main/java/com/amazon/carbonado/spi/WrappedStorage.java @@ -78,6 +78,10 @@ public abstract class WrappedStorage<S extends Storable> implements Storage<S> { return wrap(mStorage.query(filter));
}
+ public void truncate() throws PersistException {
+ mStorage.truncate();
+ }
+
public boolean addTrigger(Trigger<? super S> trigger) {
return mTriggerManager.addTrigger(trigger);
}
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<K, V, E extends Exception> {
+ private final ConcurrentMap<K, V> mValues;
+ private final WeakReentrantLockPool<K> mLockPool;
+
+ protected AbstractPool() {
+ mValues = new ConcurrentHashMap<K, V>();
+ mLockPool = new WeakReentrantLockPool<K>();
+ }
+
+ /**
+ * 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<V> 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<K, V, E extends Exception> {
+ private final ConcurrentMap<K, ValueRef<K, V>> mValues;
+ private final ReferenceQueue<V> mValueRefQueue;
+
+ protected AbstractWeakPool() {
+ mValues = new ConcurrentHashMap<K, ValueRef<K, V>>();
+ mValueRefQueue = new ReferenceQueue<V>();
+ }
+
+ /**
+ * 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:
+ *
+ * <pre>
+ * // 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();
+ * }
+ * </pre>
+ */
+ public V get(K key) throws E {
+ clean();
+
+ ValueRef<K, V> 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<K, V>(value, mValueRefQueue, key);
+ while (true) {
+ ValueRef<K, V> 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<K, V>) existingRef).mKey, existingRef);
+ }
+ }
+
+ return value;
+ }
+
+ /**
+ * Manually remove a value, returning the old value.
+ */
+ public V remove(Object key) {
+ clean();
+
+ ValueRef<K, V> 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<? extends V> 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<K, V>) ref).mKey, ref);
+ }
+ }
+
+ private static class ValueRef<K, V> extends WeakReference<V> {
+ final K mKey;
+
+ ValueRef(V value, ReferenceQueue<V> 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<K>
+ extends AbstractWeakPool<K, ReentrantLock, RuntimeException>
+{
+ 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<K>
+ extends AbstractWeakPool<K, ReentrantReadWriteLock, RuntimeException>
+{
+ private final boolean mFair;
+
+ public WeakReentrantReadWriteLockPool() {
+ this(false);
+ }
+
+ public WeakReentrantReadWriteLockPool(boolean fair) {
+ mFair = fair;
+ }
+
+ protected ReentrantReadWriteLock create(K key) {
+ return new ReentrantReadWriteLock(mFair);
+ }
+}
|