From 2246eac504e5593cdf4a489f97be85e2cc56dcf8 Mon Sep 17 00:00:00 2001
From: "Brian S. O'Neill" <bronee@gmail.com>
Date: Wed, 30 Aug 2006 01:37:27 +0000
Subject: Add core interfaces

---
 src/main/java/com/amazon/carbonado/Alias.java      |  56 +++
 .../java/com/amazon/carbonado/AlternateKeys.java   |  56 +++
 .../amazon/carbonado/ConfigurationException.java   |  46 +++
 .../com/amazon/carbonado/ConstraintException.java  |  47 +++
 .../amazon/carbonado/CorruptEncodingException.java |  54 +++
 src/main/java/com/amazon/carbonado/Cursor.java     | 157 ++++++++
 .../amazon/carbonado/FetchDeadlockException.java   |  51 +++
 .../java/com/amazon/carbonado/FetchException.java  |  46 +++
 .../carbonado/FetchInterruptedException.java       |  45 +++
 .../amazon/carbonado/FetchMultipleException.java   |  51 +++
 .../com/amazon/carbonado/FetchNoneException.java   |  51 +++
 .../amazon/carbonado/FetchTimeoutException.java    |  49 +++
 .../java/com/amazon/carbonado/Independent.java     |  65 ++++
 src/main/java/com/amazon/carbonado/Index.java      |  40 ++
 src/main/java/com/amazon/carbonado/Indexes.java    |  58 +++
 .../java/com/amazon/carbonado/IsolationLevel.java  | 115 ++++++
 src/main/java/com/amazon/carbonado/Join.java       | 120 ++++++
 src/main/java/com/amazon/carbonado/Key.java        |  43 +++
 .../carbonado/MalformedArgumentException.java      |  69 ++++
 .../amazon/carbonado/MalformedFilterException.java |  72 ++++
 .../amazon/carbonado/MalformedTypeException.java   |  53 +++
 .../com/amazon/carbonado/MismatchException.java    |  70 ++++
 src/main/java/com/amazon/carbonado/Nullable.java   |  44 +++
 .../amazon/carbonado/OptimisticLockException.java  |  85 +++++
 .../amazon/carbonado/PersistDeadlockException.java |  51 +++
 .../amazon/carbonado/PersistDeniedException.java   |  46 +++
 .../com/amazon/carbonado/PersistException.java     |  46 +++
 .../amazon/carbonado/PersistMultipleException.java |  51 +++
 .../com/amazon/carbonado/PersistNoneException.java |  51 +++
 .../amazon/carbonado/PersistTimeoutException.java  |  49 +++
 src/main/java/com/amazon/carbonado/PrimaryKey.java |  50 +++
 src/main/java/com/amazon/carbonado/Query.java      | 421 ++++++++++++++++++++
 src/main/java/com/amazon/carbonado/Repository.java | 157 ++++++++
 .../com/amazon/carbonado/RepositoryBuilder.java    | 104 +++++
 .../com/amazon/carbonado/RepositoryException.java  | 220 +++++++++++
 src/main/java/com/amazon/carbonado/Sequence.java   |  49 +++
 src/main/java/com/amazon/carbonado/Storable.java   | 424 +++++++++++++++++++++
 src/main/java/com/amazon/carbonado/Storage.java    | 133 +++++++
 .../com/amazon/carbonado/SupportException.java     |  48 +++
 .../java/com/amazon/carbonado/Transaction.java     | 120 ++++++
 src/main/java/com/amazon/carbonado/Trigger.java    | 327 ++++++++++++++++
 .../carbonado/UniqueConstraintException.java       |  47 +++
 .../amazon/carbonado/UnsupportedTypeException.java |  40 ++
 src/main/java/com/amazon/carbonado/Version.java    |  78 ++++
 .../java/com/amazon/carbonado/package-info.java    |  22 ++
 45 files changed, 4077 insertions(+)
 create mode 100644 src/main/java/com/amazon/carbonado/Alias.java
 create mode 100644 src/main/java/com/amazon/carbonado/AlternateKeys.java
 create mode 100644 src/main/java/com/amazon/carbonado/ConfigurationException.java
 create mode 100644 src/main/java/com/amazon/carbonado/ConstraintException.java
 create mode 100644 src/main/java/com/amazon/carbonado/CorruptEncodingException.java
 create mode 100644 src/main/java/com/amazon/carbonado/Cursor.java
 create mode 100644 src/main/java/com/amazon/carbonado/FetchDeadlockException.java
 create mode 100644 src/main/java/com/amazon/carbonado/FetchException.java
 create mode 100644 src/main/java/com/amazon/carbonado/FetchInterruptedException.java
 create mode 100644 src/main/java/com/amazon/carbonado/FetchMultipleException.java
 create mode 100644 src/main/java/com/amazon/carbonado/FetchNoneException.java
 create mode 100644 src/main/java/com/amazon/carbonado/FetchTimeoutException.java
 create mode 100644 src/main/java/com/amazon/carbonado/Independent.java
 create mode 100644 src/main/java/com/amazon/carbonado/Index.java
 create mode 100644 src/main/java/com/amazon/carbonado/Indexes.java
 create mode 100644 src/main/java/com/amazon/carbonado/IsolationLevel.java
 create mode 100644 src/main/java/com/amazon/carbonado/Join.java
 create mode 100644 src/main/java/com/amazon/carbonado/Key.java
 create mode 100644 src/main/java/com/amazon/carbonado/MalformedArgumentException.java
 create mode 100644 src/main/java/com/amazon/carbonado/MalformedFilterException.java
 create mode 100644 src/main/java/com/amazon/carbonado/MalformedTypeException.java
 create mode 100644 src/main/java/com/amazon/carbonado/MismatchException.java
 create mode 100644 src/main/java/com/amazon/carbonado/Nullable.java
 create mode 100644 src/main/java/com/amazon/carbonado/OptimisticLockException.java
 create mode 100644 src/main/java/com/amazon/carbonado/PersistDeadlockException.java
 create mode 100644 src/main/java/com/amazon/carbonado/PersistDeniedException.java
 create mode 100644 src/main/java/com/amazon/carbonado/PersistException.java
 create mode 100644 src/main/java/com/amazon/carbonado/PersistMultipleException.java
 create mode 100644 src/main/java/com/amazon/carbonado/PersistNoneException.java
 create mode 100644 src/main/java/com/amazon/carbonado/PersistTimeoutException.java
 create mode 100644 src/main/java/com/amazon/carbonado/PrimaryKey.java
 create mode 100644 src/main/java/com/amazon/carbonado/Query.java
 create mode 100644 src/main/java/com/amazon/carbonado/Repository.java
 create mode 100644 src/main/java/com/amazon/carbonado/RepositoryBuilder.java
 create mode 100644 src/main/java/com/amazon/carbonado/RepositoryException.java
 create mode 100644 src/main/java/com/amazon/carbonado/Sequence.java
 create mode 100644 src/main/java/com/amazon/carbonado/Storable.java
 create mode 100644 src/main/java/com/amazon/carbonado/Storage.java
 create mode 100644 src/main/java/com/amazon/carbonado/SupportException.java
 create mode 100644 src/main/java/com/amazon/carbonado/Transaction.java
 create mode 100644 src/main/java/com/amazon/carbonado/Trigger.java
 create mode 100644 src/main/java/com/amazon/carbonado/UniqueConstraintException.java
 create mode 100644 src/main/java/com/amazon/carbonado/UnsupportedTypeException.java
 create mode 100644 src/main/java/com/amazon/carbonado/Version.java
 create mode 100644 src/main/java/com/amazon/carbonado/package-info.java

(limited to 'src/main/java')

diff --git a/src/main/java/com/amazon/carbonado/Alias.java b/src/main/java/com/amazon/carbonado/Alias.java
new file mode 100644
index 0000000..b2e122c
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/Alias.java
@@ -0,0 +1,56 @@
+/*
+ * 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 alternate names for a {@link Storable} or a Storable property. An alias is used
+ * only by a repository to link to entities. Without an alias, the repository will perform
+ * a best guess at finding an entity to use. Aliases may be ignored by repositories that
+ * don't require explicitly named entities.
+ * <P>The most common use for an alias is for a JDBC repository, to link a storable to a table and
+ * its properties to the corresponding columns.  Naming conventions for databases rarely work
+ * well for class and variable names.
+ *
+ * <p>Example:<pre>
+ * <b>&#64;Alias("USER_INFO")</b>
+ * &#64;PrimaryKey("userInfoID")
+ * public interface UserInfo extends Storable&lt;UserInfo&gt; {
+ *     <b>&#64;Alias("USER_ID")</b>
+ *     long getUserInfoID();
+ *
+ *     ...
+ * }
+ * </pre>
+ *
+ * @author Brian S O'Neill
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.METHOD})
+public @interface Alias {
+
+    /**
+     * Alias values for the storage layer to select from. It will choose the
+     * first one in the list that matches one of its own entities.
+     */
+    String[] value();
+
+}
diff --git a/src/main/java/com/amazon/carbonado/AlternateKeys.java b/src/main/java/com/amazon/carbonado/AlternateKeys.java
new file mode 100644
index 0000000..9d27f12
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/AlternateKeys.java
@@ -0,0 +1,56 @@
+/*
+ * 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.*;
+
+/**
+ * List of alternate keys for a {@link Storable}.
+ *
+ * <p>Example:<pre>
+ * <b>&#64;AlternateKeys</b>({
+ *     <b>&#64;Key</b>("fullPath")
+ *     <b>&#64;Key</b>({"+name", "-parentID"})
+ * })
+ * &#64;PrimaryKey("ID")
+ * public interface FileInfo extends Storable&lt;FileInfo&gt; {
+ *     long getID();
+ *
+ *     String getFullPath();
+ *
+ *     String getName();
+ *
+ *     long getParentID();
+ *
+ *     ...
+ * }
+ * </pre>
+ *
+ * @author Brian S O'Neill
+ * @see Key
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+public @interface AlternateKeys {
+    /**
+     * A list of Key annotations.
+     */
+    Key[] value() default {};
+}
diff --git a/src/main/java/com/amazon/carbonado/ConfigurationException.java b/src/main/java/com/amazon/carbonado/ConfigurationException.java
new file mode 100644
index 0000000..c45ac60
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/ConfigurationException.java
@@ -0,0 +1,46 @@
+/*
+ * 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;
+
+/**
+ * Indicates that a {@link com.amazon.carbonado.Repository} could not be
+ * instantiated given the configuration information provided to a {@link
+ * com.amazon.carbonado.RepositoryBuilder}.
+ *
+ * @author Don Schneider
+ */
+public class ConfigurationException extends SupportException {
+
+    private static final long serialVersionUID = 5202481033055452633L;
+
+    public ConfigurationException() {
+        super();
+    }
+
+    public ConfigurationException(Throwable cause) {
+        super(cause);
+    }
+
+    public ConfigurationException(String message) {
+        super(message);
+    }
+
+    public ConfigurationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/ConstraintException.java b/src/main/java/com/amazon/carbonado/ConstraintException.java
new file mode 100644
index 0000000..27ae306
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/ConstraintException.java
@@ -0,0 +1,47 @@
+/*
+ * 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;
+
+/**
+ * A ConstraintException is thrown if the {@link Repository} storage layer
+ * detects any kind of constraint violation.
+ *
+ * @author Brian S O'Neill
+ */
+public class ConstraintException extends PersistException {
+
+    private static final long serialVersionUID = -6195914735758203580L;
+
+    public ConstraintException() {
+        super();
+    }
+
+    public ConstraintException(String message) {
+        super(message);
+    }
+
+    public ConstraintException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public ConstraintException(Throwable cause) {
+        super(cause);
+    }
+}
+
diff --git a/src/main/java/com/amazon/carbonado/CorruptEncodingException.java b/src/main/java/com/amazon/carbonado/CorruptEncodingException.java
new file mode 100644
index 0000000..b4dcf77
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/CorruptEncodingException.java
@@ -0,0 +1,54 @@
+/*
+ * 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;
+
+/**
+ * A CorruptEncodingException is caused when decoding an encoded record fails.
+ *
+ * @author Brian S O'Neill
+ */
+public class CorruptEncodingException extends FetchException {
+
+    private static final long serialVersionUID = 4543503149683482362L;
+
+    public CorruptEncodingException() {
+        super();
+    }
+
+    public CorruptEncodingException(String message) {
+        super(message);
+    }
+
+    public CorruptEncodingException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public CorruptEncodingException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * @param expectedGeneration expected layout generation of decoded storable
+     * @param actualGeneration actual layout generation of decoded storable
+     */
+    public CorruptEncodingException(int expectedGeneration, int actualGeneration) {
+        super("Expected layout generation of " + expectedGeneration +
+              ", but actual layout generation was " + actualGeneration);
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/Cursor.java b/src/main/java/com/amazon/carbonado/Cursor.java
new file mode 100644
index 0000000..00b2dbc
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/Cursor.java
@@ -0,0 +1,157 @@
+/*
+ * 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.util.Collection;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+/**
+ * Represents the results of a {@link com.amazon.carbonado.Query}'s fetch
+ * operation. Cursors must be closed promptly when no longer
+ * needed. Failure to do so may result in excessive resource consumption or
+ * deadlock. As a convenience, the close operation is automatically performed
+ * when the end is reached or when an exception is thrown.
+ *
+ * <P>Note: because a Cursor manages resources, it is inapproprate to create a long-lived one and
+ * pass it around in your code.  A cursor is expected to live close to the Query which vended
+ * it.  To discourage inappropriate retention, the cursor does not implement methods (like
+ * "getQuery" or "reset") which would make it more convenient to operate on in isolation.
+ *
+ * <P>Similarly, it is difficult to guarantee that the results of a cursor will
+ * be the same in case of a "reset" or reverse iteration.  For this reason,
+ * neither is supported; if you need to iterate the same set of objects twice,
+ * simply retain the query object and reissue it. Be aware that the results may
+ * not be identical, if any relevant objects are added to or removed the
+ * repository in the interim. To guard against this, operate within a
+ * serializable {@link IsolationLevel isolation level}.
+ *
+ * <p>Cursor instances are mutable, but they should be thread-safe. Still, only
+ * one thread should ever operate on a cursor instance.
+ *
+ * @author Brian S O'Neill
+ * @author Don Schneider
+ */
+public interface Cursor<S> {
+    /**
+     * Call close to release any resources being held by this cursor. Further
+     * operations on this cursor will behave as if there are no results.
+     */
+    void close() throws FetchException;
+
+    /**
+     * Returns true if this cursor has more elements. In other words, returns
+     * true if {@link #next next} would return an element rather than throwing
+     * an exception.
+     *
+     * @throws FetchException if storage layer throws an exception
+     */
+    boolean hasNext() throws FetchException;
+
+    /**
+     * Returns the next element from this cursor. This method may be called
+     * repeatedly to iterate through the results.
+     *
+     * @throws FetchException if storage layer throws an exception
+     * @throws NoSuchElementException if the cursor has no next element.
+     */
+    S next() throws FetchException;
+
+    /**
+     * Skips forward by the specified amount of elements, returning the actual
+     * amount skipped. The actual amount may be less than the requested amount
+     * if the end of the results was reached.
+     *
+     * @param amount maximum amount of elements to skip
+     * @return actual amount skipped
+     * @throws FetchException if storage layer throws an exception
+     * @throws IllegalArgumentException if amount is negative
+     */
+    int skipNext(int amount) throws FetchException;
+
+    /**
+     * Copies all remaining next elements into the given collection. This
+     * method is roughly equivalent to the following:
+     * <pre>
+     * Cursor cursor;
+     * ...
+     * while (cursor.hasNext()) {
+     *     c.add(cursor.next());
+     * }
+     * </pre>
+     *
+     * <p>As a side-effect of calling this method, the cursor is closed.
+     *
+     * @return actual amount of results added
+     * @throws FetchException if storage layer throws an exception
+     */
+    int copyInto(Collection<? super S> c) throws FetchException;
+
+    /**
+     * Copies a limited amount of remaining next elements into the given
+     * collection. This method is roughly equivalent to the following:
+     * <pre>
+     * Cursor cursor;
+     * ...
+     * while (--limit >= 0 && cursor.hasNext()) {
+     *     c.add(cursor.next());
+     * }
+     * </pre>
+     *
+     * @param limit maximum amount of elements to copy
+     * @return actual amount of results added
+     * @throws FetchException if storage layer throws an exception
+     * @throws IllegalArgumentException if limit is negative
+     */
+    int copyInto(Collection<? super S> c, int limit) throws FetchException;
+
+    /**
+     * Copies all remaining next elements into a new modifiable list. This
+     * method is roughly equivalent to the following:
+     * <pre>
+     * Cursor&lt;S&gt; cursor;
+     * ...
+     * List&lt;S&gt; list = new ...
+     * cursor.copyInto(list);
+     * </pre>
+     *
+     * <p>As a side-effect of calling this method, the cursor is closed.
+     *
+     * @return a new modifiable list
+     * @throws FetchException if storage layer throws an exception
+     */
+    List<S> toList() throws FetchException;
+
+    /**
+     * Copies a limited amount of remaining next elements into a new modifiable
+     * list. This method is roughly equivalent to the following:
+     * <pre>
+     * Cursor&lt;S&gt; cursor;
+     * ...
+     * List&lt;S&gt; list = new ...
+     * cursor.copyInto(list, limit);
+     * </pre>
+     *
+     * @param limit maximum amount of elements to copy
+     * @return a new modifiable list
+     * @throws FetchException if storage layer throws an exception
+     * @throws IllegalArgumentException if limit is negative
+     */
+    List<S> toList(int limit) throws FetchException;
+}
diff --git a/src/main/java/com/amazon/carbonado/FetchDeadlockException.java b/src/main/java/com/amazon/carbonado/FetchDeadlockException.java
new file mode 100644
index 0000000..485231d
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/FetchDeadlockException.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+/**
+ * Thrown if a fetch operation fails because it was selected to resolve a
+ * deadlock.
+ *
+ * @author Brian S O'Neill
+ */
+public class FetchDeadlockException extends FetchException {
+
+    private static final long serialVersionUID = -1305211848299678161L;
+
+    public FetchDeadlockException() {
+        super();
+    }
+
+    public FetchDeadlockException(String message) {
+        super(message);
+    }
+
+    public FetchDeadlockException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public FetchDeadlockException(Throwable cause) {
+        super(cause);
+    }
+
+    @Override
+    protected PersistException makePersistException(String message, Throwable cause) {
+        return new PersistDeadlockException(message, cause);
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/FetchException.java b/src/main/java/com/amazon/carbonado/FetchException.java
new file mode 100644
index 0000000..a8af267
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/FetchException.java
@@ -0,0 +1,46 @@
+/*
+ * 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;
+
+/**
+ * A FetchException is caused by a failure when selecting records from a {@link
+ * Repository}.
+ *
+ * @author Brian S O'Neill
+ */
+public class FetchException extends RepositoryException {
+
+    private static final long serialVersionUID = 3261554550881322593L;
+
+    public FetchException() {
+        super();
+    }
+
+    public FetchException(String message) {
+        super(message);
+    }
+
+    public FetchException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public FetchException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/FetchInterruptedException.java b/src/main/java/com/amazon/carbonado/FetchInterruptedException.java
new file mode 100644
index 0000000..1f8e4a7
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/FetchInterruptedException.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;
+
+/**
+ * Thrown from a fetch operation that was canceled.
+ *
+ * @author Brian S O'Neill
+ */
+public class FetchInterruptedException extends FetchException {
+
+    private static final long serialVersionUID = -1708236034888616065L;
+
+    public FetchInterruptedException() {
+        super();
+    }
+
+    public FetchInterruptedException(String message) {
+        super(message);
+    }
+
+    public FetchInterruptedException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public FetchInterruptedException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/FetchMultipleException.java b/src/main/java/com/amazon/carbonado/FetchMultipleException.java
new file mode 100644
index 0000000..0fbc34e
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/FetchMultipleException.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+/**
+ * A FetchMultipleException is thrown when a fetch operation returned more
+ * than one record when at most one was expected.
+ *
+ * @author Brian S O'Neill
+ */
+public class FetchMultipleException extends FetchException {
+
+    private static final long serialVersionUID = -7126472358604146147L;
+
+    public FetchMultipleException() {
+        super();
+    }
+
+    public FetchMultipleException(String message) {
+        super(message);
+    }
+
+    public FetchMultipleException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public FetchMultipleException(Throwable cause) {
+        super(cause);
+    }
+
+    @Override
+    protected PersistException makePersistException(String message, Throwable cause) {
+        return new PersistMultipleException(message, cause);
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/FetchNoneException.java b/src/main/java/com/amazon/carbonado/FetchNoneException.java
new file mode 100644
index 0000000..dfc4dfe
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/FetchNoneException.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+/**
+ * A FetchNoneException is thrown when a fetch operation returned no records
+ * when at least one was expected.
+ *
+ * @author Brian S O'Neill
+ */
+public class FetchNoneException extends FetchException {
+
+    private static final long serialVersionUID = -3271539536650048094L;
+
+    public FetchNoneException() {
+        super();
+    }
+
+    public FetchNoneException(String message) {
+        super(message);
+    }
+
+    public FetchNoneException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public FetchNoneException(Throwable cause) {
+        super(cause);
+    }
+
+    @Override
+    protected PersistException makePersistException(String message, Throwable cause) {
+        return new PersistNoneException(message, cause);
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/FetchTimeoutException.java b/src/main/java/com/amazon/carbonado/FetchTimeoutException.java
new file mode 100644
index 0000000..bb5eb10
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/FetchTimeoutException.java
@@ -0,0 +1,49 @@
+/*
+ * 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;
+
+/**
+ * Thrown if a fetch operation fails because lock acquisition timed out.
+ *
+ * @author Brian S O'Neill
+ */
+public class FetchTimeoutException extends FetchException {
+    private static final long serialVersionUID = 1L;
+
+    public FetchTimeoutException() {
+        super();
+    }
+
+    public FetchTimeoutException(String message) {
+        super(message);
+    }
+
+    public FetchTimeoutException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public FetchTimeoutException(Throwable cause) {
+        super(cause);
+    }
+
+    @Override
+    protected PersistException makePersistException(String message, Throwable cause) {
+        return new PersistTimeoutException(message, cause);
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/Independent.java b/src/main/java/com/amazon/carbonado/Independent.java
new file mode 100644
index 0000000..f240fe5
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/Independent.java
@@ -0,0 +1,65 @@
+/*
+ * 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.*;
+
+/**
+ * A hint for a dependent {@link Repository} to allow a {@link Storable} property or type
+ * to be undefined in the underlying schema.  Ordinarily, if a dependent repository cannot
+ * find a matching property, it throws {@link MismatchException} when the {@link Storage} is
+ * first retrieved for the storable. This annotation suppresses that exception, and instead
+ * makes the property or type unsupported.  Any subsequent invocation of a property access
+ * method for the independent type or property will cause an UnsupportedOperationException
+ * to be thrown.
+ * <p>
+ * One example of when this might be used would be to store a calculated field in the cached
+ * representation of the object.  It is <b>not</b> necessary to prevent implemented methods
+ * of the form "get&lt;value&gt;" from being inadvertently interpreted as properties of the
+ * storable; any implementation is by definition not a property.
+ * <p>
+ * If a correctly matching property actually is found, then this annotation is
+ * ignored and the property or type is defined as usual. If the Repository
+ * finds a property whose name matches, but whose type does not match, a
+ * MismatchException will be thrown regardless of this annotation.
+ *
+ * <P>Independent repositories completely ignore this annotation.
+ *
+ * <p>Example:<pre>
+ * public interface UserInfo extends Storable&lt;UserInfo&gt; {
+ *     <b>&#64;Independent</b>
+ *     String getName();
+ *
+ *     ...
+ * }
+ * </pre>
+ *
+ * <b>Note:</b> If a {@link Version versioned} Storable with an independent
+ * property is managed by a replicating repository, updates which modify just
+ * the independent property still update the master Storable, in order to get a
+ * new record version. Therefore, independent properties should not be used as
+ * a performance enhancement which avoids writes to a master repository.
+ *
+ * @author Brian S O'Neill
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.METHOD})
+public @interface Independent {
+}
diff --git a/src/main/java/com/amazon/carbonado/Index.java b/src/main/java/com/amazon/carbonado/Index.java
new file mode 100644
index 0000000..755ecfe
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/Index.java
@@ -0,0 +1,40 @@
+/*
+ * 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.*;
+
+/**
+ * An Index annotation provides a hint to the storage layer allowing {@link
+ * Query queries} to be performed more quickly. Indexes are contained by an
+ * {@link Indexes} annotation.
+ *
+ * @author Brian S O'Neill
+ * @see Indexes
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({})
+public @interface Index {
+    /**
+     * A list of property names, which may be prefixed with '+' or '-' to
+     * indicate a preference for ascending or descending order.
+     */
+    String[] value();
+}
diff --git a/src/main/java/com/amazon/carbonado/Indexes.java b/src/main/java/com/amazon/carbonado/Indexes.java
new file mode 100644
index 0000000..482f955
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/Indexes.java
@@ -0,0 +1,58 @@
+/*
+ * 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.*;
+
+/**
+ * The Indexes annotation is applied to a {@link Storable} for specifying all
+ * the {@link Index indexes} it should have.
+ *
+ * <p>Example:<pre>
+ * <b>&#64;Indexes</b>({
+ *     <b>&#64;Index</b>("name"),
+ *     <b>&#64;Index</b>("-lastModified"),
+ *     <b>&#64;Index</b>({"length", "lastModified"})
+ * })
+ * &#64;PrimaryKey("ID")
+ * public interface FileInfo extends Storable&lt;FileInfo&gt; {
+ *     long getID();
+ *
+ *     String getName();
+ *
+ *     long getLength();
+ *
+ *     long getLastModified();
+ *
+ *     ...
+ * }
+ * </pre>
+ *
+ * @author Brian S O'Neill
+ * @see Index
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+public @interface Indexes {
+    /**
+     * A list of Index annotations.
+     */
+    Index[] value() default {};
+}
diff --git a/src/main/java/com/amazon/carbonado/IsolationLevel.java b/src/main/java/com/amazon/carbonado/IsolationLevel.java
new file mode 100644
index 0000000..5f963b2
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/IsolationLevel.java
@@ -0,0 +1,115 @@
+/*
+ * 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;
+
+/**
+ * Describes a transaction isolation level. Transaction levels, in order from
+ * lowest to highest are:
+ *
+ * <ul>
+ * <li>{@link #READ_UNCOMMITTED}
+ * <li>{@link #READ_COMMITTED}
+ * <li>{@link #REPEATABLE_READ}
+ * <li>{@link #SERIALIZABLE}
+ * </ul>
+ *
+ * A transaction's isolation level is usually {@code READ_COMMITTED} or
+ * {@code REPEATABLE_READ} by default. Forcing a lower level, like
+ * {@code READ_COMMITTED}, is useful when performing a long cursor
+ * iteration. It releases locks during iteration rather than holding on to them
+ * until the transaction exits.
+ *
+ * @author Brian S O'Neill
+ * @see Repository#enterTransaction(IsolationLevel)
+ * @see Transaction
+ */
+public enum IsolationLevel {
+
+    /**
+     * Indicates that dirty reads, non-repeatable reads and phantom reads can
+     * occur. This level allows modifications by one transaction to be read by
+     * another transaction before any changes have been committed (a "dirty
+     * read"). If any of the changes are rolled back, the second transaction
+     * will have retrieved an invalid modification.
+     *
+     * <p>This level is also known as degree 1 isolation.
+     */
+    READ_UNCOMMITTED,
+
+    /**
+     * Indicates that dirty reads are prevented. Non-repeatable reads and
+     * phantom reads can occur. This level only prohibits a transaction from
+     * reading modifications with uncommitted changes in it.
+     *
+     * <p>This level is also known as degree 2 isolation.
+     */
+    READ_COMMITTED,
+
+    /**
+     * Indicates that dirty reads and non-repeatable reads are prevented.
+     * Phantom reads can occur. This level prohibits a transaction from reading
+     * uncommitted changes, and it also prohibits the situation where one
+     * transaction reads a record, a second transaction alters the record, and
+     * the first transaction rereads the record, getting different values the
+     * second time (a "non-repeatable read").
+     */
+    REPEATABLE_READ,
+
+    /**
+     * Indicates that dirty reads, non-repeatable reads and phantom reads are
+     * prevented. Phantoms are records returned as a result of a search, but
+     * which were not seen by the same transaction when the identical search
+     * criteria was previously used. For example, another transaction may have
+     * inserted records which match the original search.
+     *
+     * <p>This level is also known as degree 3 isolation.
+     */
+    SERIALIZABLE;
+
+    /**
+     * Returns true if this isolation level is at least as high as the one
+     * given.
+     */
+    public boolean isAtLeast(IsolationLevel level) {
+        return ordinal() >= level.ordinal();
+    }
+
+    /**
+     * Returns true if this isolation level is no higher than the one given.
+     */
+    public boolean isAtMost(IsolationLevel level) {
+        return ordinal() <= level.ordinal();
+    }
+
+    /**
+     * Returns the lowest common isolation level between this and the one
+     * given.
+     */
+    public IsolationLevel lowestCommon(IsolationLevel level) {
+        return (ordinal() >= level.ordinal()) ? level : this;
+    }
+
+    /**
+     * Returns the highest common isolation level between this and the one
+     * given.
+     */
+    public IsolationLevel highestCommon(IsolationLevel level) {
+        return (ordinal() <= level.ordinal()) ? level : this;
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/Join.java b/src/main/java/com/amazon/carbonado/Join.java
new file mode 100644
index 0000000..eb21e58
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/Join.java
@@ -0,0 +1,120 @@
+/*
+ * 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 as defining a join relationship
+ * with another Storable type. Joins can also refer to their own enclosing
+ * class or interface.
+ * <p>
+ * To complete the join, lists of internal and external properties may be
+ * supplied. If these lists are not supplied, then join is "natural", and the
+ * properties are determined automatically. When the lists are specified, the
+ * join is "explicit". Natural joins are merely a convenience; they can always
+ * be replaced by an explicit join.
+ * <p>
+ * The lists used for explicit joins must have the same length, and each must
+ * have at least one element. Each element in the internal list must refer to
+ * a property defined in this property's class or interface, and each element
+ * in the external list must refer to a matching property defined in the joined
+ * type. The matched up property pairs must not themselves be join properties,
+ * and they must be compatible with each other.
+ * <p>
+ * If the join is made to external properties which do not completely specify a
+ * primary key, then the type of the join property must be a {@link Query} of
+ * the joined type. When the type is a Query, a property mutator method cannot
+ * be defined. The returned query has all of the "with" parameters filled in.
+ * <p>
+ * With a natural join, the internal and external properties are deduced by
+ * examining the type of the referenced join property. If the type is a Query,
+ * then the internal and external properties are set to match this property's
+ * primary key. The referenced join property (specified as a parameterized type
+ * to Query) must have properties matching name and type of this property's
+ * primary key.
+ * <p>
+ * If a natural join's property type is not defined by a Query, then the
+ * internal and external properties are set to match the referenced property's
+ * primary key. This join property must have properties matching name and type
+ * of the referenced property's primary key.
+ *
+ * <p>Example:<pre>
+ * &#64;PrimaryKey("addressID")
+ * public interface Address extends Storable {
+ *     int getAddressID();
+ *
+ *     ...
+ * }
+ *
+ * &#64;PrimaryKey("userID")
+ * public interface UserInfo extends Storable {
+ *     int getUserID();
+ *
+ *     int getAddressID();
+ *
+ *     void setAddressID(int value);
+ *
+ *     // Natural join, which works because Address has a primary key
+ *     // property of addressID which matches a property in this type.
+ *     <b>&#64;Join</b>
+ *     Address getAddress() throws FetchException;
+ *
+ *     // Explicit join, equivalent to getAddress.
+ *     <b>&#64;Join(internal="addressID", external="addressID")</b>
+ *     Address getCurrentAddress() throws FetchException;
+ *
+ *     &#64;Nullable
+ *     Integer getParentID();
+ *
+ *     void setParentID(Integer value);
+ *
+ *     // Many-to-one relationship
+ *     &#64;Nullable
+ *     <b>&#64;Join(internal="parentID", external="userID")</b>
+ *     UserInfo getParent() throws FetchException;
+ *
+ *     // One-to-many relationship
+ *     <b>&#64;Join(internal="userID", external="parentID")</b>
+ *     Query&lt;UserInfo&gt; getChildren() throws FetchException;
+ *
+ *     ...
+ * }
+ * </pre>
+ *
+ * @author Brian S O'Neill
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD})
+public @interface Join {
+
+    /**
+     * List of property names defined in this property's enclosing class or
+     * interface.
+     */
+    String[] internal() default {};
+
+    /**
+     * List of property names defined in the foreign property's enclosing class
+     * or interface.
+     */
+    String[] external() default {};
+
+}
diff --git a/src/main/java/com/amazon/carbonado/Key.java b/src/main/java/com/amazon/carbonado/Key.java
new file mode 100644
index 0000000..01db2b8
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/Key.java
@@ -0,0 +1,43 @@
+/*
+ * 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 as being a member of an alternate
+ * key. An alternate key is just as good as the primary key for uniquely
+ * identifying a storable instance, except repositories are usually more
+ * flexible with alternate keys. For example, dropping an alternate key and
+ * reconstructing it should not result in loss of data. Alternate keys are
+ * often implemented as indexes with a uniqueness constraint.
+ *
+ * @author Brian S O'Neill
+ * @see AlternateKeys
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({})
+public @interface Key {
+    /**
+     * A list of property names, which may be prefixed with '+' or '-' to
+     * indicate a preference for ascending or descending order.
+     */
+    String[] value();
+}
diff --git a/src/main/java/com/amazon/carbonado/MalformedArgumentException.java b/src/main/java/com/amazon/carbonado/MalformedArgumentException.java
new file mode 100644
index 0000000..265974c
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/MalformedArgumentException.java
@@ -0,0 +1,69 @@
+/*
+ * 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.util.Collections;
+import java.util.List;
+
+/**
+ * A MalformedArgumentException is thrown after detailed analysis on an
+ * argument determined it was not suitable. This class is abstract to prevent
+ * its direct use. Subclasses are encouraged to provide more detail as to the
+ * cause of the exception.
+ *
+ * @author Brian S O'Neill
+ */
+public abstract class MalformedArgumentException extends IllegalArgumentException {
+
+    private static final long serialVersionUID = 1L;
+
+    private List<String> mMessages;
+
+    protected MalformedArgumentException() {
+        super();
+    }
+
+    protected MalformedArgumentException(String message) {
+        super(message);
+    }
+
+    protected MalformedArgumentException(List<String> messages) {
+        super();
+        mMessages = Collections.unmodifiableList(messages);
+    }
+
+    public String getMessage() {
+        if (mMessages == null || mMessages.size() == 0) {
+            return super.getMessage();
+        }
+        return mMessages.get(0);
+    }
+
+    /**
+     * Multiple error messages may be embedded in a MalformedArgumentException.
+     *
+     * @return non-null, unmodifiable list of messages
+     */
+    public List<String> getMessages() {
+        if (mMessages == null || mMessages.size() == 0) {
+            mMessages = Collections.singletonList(super.getMessage());
+        }
+        return mMessages;
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/MalformedFilterException.java b/src/main/java/com/amazon/carbonado/MalformedFilterException.java
new file mode 100644
index 0000000..0333c10
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/MalformedFilterException.java
@@ -0,0 +1,72 @@
+/*
+ * 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;
+
+/**
+ * A MalformedFilterException indicates that a
+ * {@link Storage#query(String) query filter} is invalid.
+ *
+ * @author Brian S O'Neill
+ */
+public class MalformedFilterException extends MalformedArgumentException {
+
+    private static final long serialVersionUID = -6917096146885017974L;
+
+    private final String mFilter;
+    private final int mIndex;
+
+    public MalformedFilterException(String filter) {
+        super(filter);
+        mFilter = filter;
+        mIndex = -1;
+    }
+
+    public MalformedFilterException(String filter, int index) {
+        super(filter);
+        mFilter = filter;
+        mIndex = index;
+    }
+
+    public MalformedFilterException(String filter, String message) {
+        super(message);
+        mFilter = filter;
+        mIndex = -1;
+    }
+
+    public MalformedFilterException(String filter, String message, int index) {
+        super(message);
+        mFilter = filter;
+        mIndex = index;
+    }
+
+    /**
+     * Returns the malformed query filter.
+     */
+    public String getFilter() {
+        return mFilter;
+    }
+
+    /**
+     * Returns the index into the query filter string that is malformed, or a
+     * negative value if not known.
+     */
+    public int getCulpritIndex() {
+        return mIndex;
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/MalformedTypeException.java b/src/main/java/com/amazon/carbonado/MalformedTypeException.java
new file mode 100644
index 0000000..45ad528
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/MalformedTypeException.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.util.List;
+
+/**
+ * A MalformedTypeException indicates that a {@link Storable} is defined in a
+ * way that violates the requirements for Storable objects.
+ *
+ * @author Brian S O'Neill
+ */
+public class MalformedTypeException extends MalformedArgumentException {
+
+    private static final long serialVersionUID = 5463649671507513977L;
+
+    private final Class<?> mType;
+
+    public MalformedTypeException(Class<?> malformedType) {
+        super();
+        mType = malformedType;
+    }
+
+    public MalformedTypeException(Class<?> malformedType, String message) {
+        super(message);
+        mType = malformedType;
+    }
+
+    public MalformedTypeException(Class<?> malformedType, List<String> messages) {
+        super(messages);
+        mType = malformedType;
+    }
+
+    public Class<?> getMalformedType() {
+        return mType;
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/MismatchException.java b/src/main/java/com/amazon/carbonado/MismatchException.java
new file mode 100644
index 0000000..ddd64f6
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/MismatchException.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;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Indicates that a {@link Storable} doesn't exactly match up with an external
+ * schema. This exception may only be thrown by repositories with a dependency
+ * on an external schema.
+ *
+ * @author Brian S O'Neill
+ */
+public class MismatchException extends SupportException {
+
+    private static final long serialVersionUID = 5840495857407789424L;
+
+    private List<String> mMessages;
+
+    public MismatchException() {
+        super();
+        mMessages = null;
+    }
+
+    public MismatchException(String message) {
+        super(message);
+        mMessages = null;
+    }
+
+    public MismatchException(List<String> messages) {
+        super();
+        mMessages = Collections.unmodifiableList(messages);
+    }
+
+    public String getMessage() {
+        if (mMessages == null || mMessages.size() == 0) {
+            return super.getMessage();
+        }
+        return mMessages.get(0);
+    }
+
+    /**
+     * Multiple error messages may be embedded in a MismatchException.
+     *
+     * @return non-null, unmodifiable list of messages
+     */
+    public List<String> getMessages() {
+        if (mMessages == null || mMessages.size() == 0) {
+            mMessages = Collections.singletonList(super.getMessage());
+        }
+        return mMessages;
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/Nullable.java b/src/main/java/com/amazon/carbonado/Nullable.java
new file mode 100644
index 0000000..c8ac923
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/Nullable.java
@@ -0,0 +1,44 @@
+/*
+ * 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 that a {@link Storable} property can have a null value. By
+ * default, all Storable properties are required to have a non-null value. It
+ * is illegal to declare a property as nullable whose type is a primitive
+ * non-object.
+ *
+ * <p>Example:<pre>
+ * public interface UserInfo extends Storable&lt;UserInfo&gt; {
+ *     <b>&#64;Nullable</b>
+ *     String getName();
+ *
+ *     ...
+ * }
+ * </pre>
+ *
+ * @author Brian S O'Neill
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD})
+public @interface Nullable {
+}
diff --git a/src/main/java/com/amazon/carbonado/OptimisticLockException.java b/src/main/java/com/amazon/carbonado/OptimisticLockException.java
new file mode 100644
index 0000000..2a782d1
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/OptimisticLockException.java
@@ -0,0 +1,85 @@
+/*
+ * 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;
+
+/**
+ * An OptimisticLockException is thrown if the {@link Repository} is using
+ * optimistic locking for concurrency control, and lock aquisition failed.
+ *
+ * @author Brian S O'Neill
+ */
+public class OptimisticLockException extends PersistException {
+
+    private static final long serialVersionUID = 4081788711829580886L;
+
+    public OptimisticLockException() {
+        super();
+    }
+
+    public OptimisticLockException(String message) {
+        super(message);
+    }
+
+    public OptimisticLockException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public OptimisticLockException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * @param expectedVersion version number that was expected for persistent
+     * record when update was executed
+     */
+    public OptimisticLockException(long expectedVersion) {
+        this((Long) expectedVersion);
+    }
+
+    /**
+     * @param expectedVersion version number that was expected for persistent
+     * record when update was executed
+     */
+    public OptimisticLockException(Object expectedVersion) {
+        this(expectedVersion, null);
+    }
+
+    /**
+     * @param expectedVersion version number that was expected for persistent
+     * record when update was executed
+     * @param savedVersion actual persistent version number of storable
+     */
+    public OptimisticLockException(Object expectedVersion, Object savedVersion) {
+        super(makeMessage(expectedVersion, savedVersion));
+    }
+
+    private static String makeMessage(Object expectedVersion, Object savedVersion) {
+        if (expectedVersion == null) {
+            if (savedVersion == null) {
+                return null;
+            }
+            return "Failed to update because saved version is " + savedVersion;
+        }
+        if (savedVersion == null) {
+            return "Failed to update for expected version " + expectedVersion;
+        }
+        return "Failed to update for expected version " + expectedVersion +
+            " because saved version is " + savedVersion;
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/PersistDeadlockException.java b/src/main/java/com/amazon/carbonado/PersistDeadlockException.java
new file mode 100644
index 0000000..1e807dd
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/PersistDeadlockException.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+/**
+ * Thrown if a persist operation fails because it was selected to resolve a
+ * deadlock.
+ *
+ * @author Brian S O'Neill
+ */
+public class PersistDeadlockException extends PersistException {
+
+    private static final long serialVersionUID = 1932137921808640293L;
+
+    public PersistDeadlockException() {
+        super();
+    }
+
+    public PersistDeadlockException(String message) {
+        super(message);
+    }
+
+    public PersistDeadlockException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public PersistDeadlockException(Throwable cause) {
+        super(cause);
+    }
+
+    @Override
+    protected FetchException makeFetchException(String message, Throwable cause) {
+        return new FetchDeadlockException(message, cause);
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/PersistDeniedException.java b/src/main/java/com/amazon/carbonado/PersistDeniedException.java
new file mode 100644
index 0000000..f3dd0d6
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/PersistDeniedException.java
@@ -0,0 +1,46 @@
+/*
+ * 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;
+
+/**
+ * Thrown when a persist operation was denied either because the repository is
+ * in read-only mode, or the connection does not have permission.
+ *
+ * @author Brian S O'Neill
+ */
+public class PersistDeniedException extends PersistException {
+
+    private static final long serialVersionUID = 7235477858113097814L;
+
+    public PersistDeniedException() {
+        super();
+    }
+
+    public PersistDeniedException(String message) {
+        super(message);
+    }
+
+    public PersistDeniedException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public PersistDeniedException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/PersistException.java b/src/main/java/com/amazon/carbonado/PersistException.java
new file mode 100644
index 0000000..9436682
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/PersistException.java
@@ -0,0 +1,46 @@
+/*
+ * 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;
+
+/**
+ * A PersistException is caused by a failure when inserting, updating, or
+ * deleting records from a {@link Repository}.
+ *
+ * @author Brian S O'Neill
+ */
+public class PersistException extends RepositoryException {
+
+    private static final long serialVersionUID = 7900595110884888593L;
+
+    public PersistException() {
+        super();
+    }
+
+    public PersistException(String message) {
+        super(message);
+    }
+
+    public PersistException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public PersistException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/PersistMultipleException.java b/src/main/java/com/amazon/carbonado/PersistMultipleException.java
new file mode 100644
index 0000000..3bae443
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/PersistMultipleException.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+/**
+ * A PersistMultipleException is thrown when a persist operation would have
+ * applied to more than one record when at most one was expected.
+ *
+ * @author Brian S O'Neill
+ */
+public class PersistMultipleException extends PersistException {
+
+    private static final long serialVersionUID = -7671067829937508160L;
+
+    public PersistMultipleException() {
+        super();
+    }
+
+    public PersistMultipleException(String message) {
+        super(message);
+    }
+
+    public PersistMultipleException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public PersistMultipleException(Throwable cause) {
+        super(cause);
+    }
+
+    @Override
+    protected FetchException makeFetchException(String message, Throwable cause) {
+        return new FetchMultipleException(message, cause);
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/PersistNoneException.java b/src/main/java/com/amazon/carbonado/PersistNoneException.java
new file mode 100644
index 0000000..a6436d2
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/PersistNoneException.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+/**
+ * A PersistNoneException is thrown when a persist operation applied to no
+ * records when at least one was expected.
+ *
+ * @author Brian S O'Neill
+ */
+public class PersistNoneException extends PersistException {
+
+    private static final long serialVersionUID = -3271539536650048094L;
+
+    public PersistNoneException() {
+        super();
+    }
+
+    public PersistNoneException(String message) {
+        super(message);
+    }
+
+    public PersistNoneException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public PersistNoneException(Throwable cause) {
+        super(cause);
+    }
+
+    @Override
+    protected FetchException makeFetchException(String message, Throwable cause) {
+        return new FetchNoneException(message, cause);
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/PersistTimeoutException.java b/src/main/java/com/amazon/carbonado/PersistTimeoutException.java
new file mode 100644
index 0000000..a1ed6ad
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/PersistTimeoutException.java
@@ -0,0 +1,49 @@
+/*
+ * 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;
+
+/**
+ * Thrown if a persist operation fails because lock acquisition timed out.
+ *
+ * @author Brian S O'Neill
+ */
+public class PersistTimeoutException extends PersistException {
+    private static final long serialVersionUID = 1L;
+
+    public PersistTimeoutException() {
+        super();
+    }
+
+    public PersistTimeoutException(String message) {
+        super(message);
+    }
+
+    public PersistTimeoutException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public PersistTimeoutException(Throwable cause) {
+        super(cause);
+    }
+
+    @Override
+    protected FetchException makeFetchException(String message, Throwable cause) {
+        return new FetchTimeoutException(message, cause);
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/PrimaryKey.java b/src/main/java/com/amazon/carbonado/PrimaryKey.java
new file mode 100644
index 0000000..ffea9be
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/PrimaryKey.java
@@ -0,0 +1,50 @@
+/*
+ * 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 as being a member of the primary
+ * key. All Storable types must have at least one property belonging to the
+ * primary key.
+ *
+ * <p>Example:<pre>
+ * <b>&#64;PrimaryKey</b>("userInfoID")
+ * public interface UserInfo extends Storable&lt;UserInfo&gt; {
+ *     long getUserInfoID();
+ *
+ *     ...
+ * }
+ * </pre>
+ *
+ * @author Brian S O'Neill
+ * @see AlternateKeys
+ * @see Sequence
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+public @interface PrimaryKey {
+    /**
+     * A list of property names, which may be prefixed with '+' or '-' to
+     * indicate a preference for ascending or descending order.
+     */
+    String[] value();
+}
diff --git a/src/main/java/com/amazon/carbonado/Query.java b/src/main/java/com/amazon/carbonado/Query.java
new file mode 100644
index 0000000..6073a84
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/Query.java
@@ -0,0 +1,421 @@
+/*
+ * 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.io.IOException;
+
+import com.amazon.carbonado.filter.Filter;
+import com.amazon.carbonado.filter.FilterValues;
+
+/**
+ * Supports complex retrieval and deletion of {@link Storable} objects. A Query
+ * is defined by {@link Storage#query}, and it merely represents an action. It
+ * does not contain any data, and it is immutable. Query objects are usually
+ * compiled and cached, and the same instance can be re-used for future
+ * queries.
+ *
+ * <p>Query instances are thread-safe and immutable. All of the apparent
+ * mutators (with, et al) do not modify the Query, but instead return a new
+ * Query with the requested modification.
+ *
+ * @author Brian S O'Neill
+ */
+public interface Query<S extends Storable> {
+    /**
+     * Returns the specific type of Storable managed by this object.
+     */
+    Class<S> getStorableType();
+
+    /**
+     * Returns the query's filter.
+     */
+    Filter<S> getFilter();
+
+    /**
+     * Returns the query's filter values, which is null if filter has no
+     * parameters.
+     */
+    FilterValues<S> getFilterValues();
+
+    /**
+     * Returns the amount of blank parameters that need to be filled in. If
+     * zero, then this query is ready to be used.
+     */
+    int getBlankParameterCount();
+
+    /**
+     * Returns a copy of this Query with the next blank parameter filled in.
+     *
+     * @param value parameter value to fill in
+     * @throws IllegalStateException if no blank parameters
+     * @throws IllegalArgumentException if type doesn't match
+     */
+    Query<S> with(int value);
+
+    /**
+     * Returns a copy of this Query with the next blank parameter filled in.
+     *
+     * @param value parameter value to fill in
+     * @throws IllegalStateException if no blank parameters
+     * @throws IllegalArgumentException if type doesn't match
+     */
+    Query<S> with(long value);
+
+    /**
+     * Returns a copy of this Query with the next blank parameter filled in.
+     *
+     * @param value parameter value to fill in
+     * @throws IllegalStateException if no blank parameters
+     * @throws IllegalArgumentException if type doesn't match
+     */
+    Query<S> with(float value);
+
+    /**
+     * Returns a copy of this Query with the next blank parameter filled in.
+     *
+     * @param value parameter value to fill in
+     * @throws IllegalStateException if no blank parameters
+     * @throws IllegalArgumentException if type doesn't match
+     */
+    Query<S> with(double value);
+
+    /**
+     * Returns a copy of this Query with the next blank parameter filled in.
+     *
+     * @param value parameter value to fill in
+     * @throws IllegalStateException if no blank parameters
+     * @throws IllegalArgumentException if type doesn't match
+     */
+    Query<S> with(boolean value);
+
+    /**
+     * Returns a copy of this Query with the next blank parameter filled in.
+     *
+     * @param value parameter value to fill in
+     * @throws IllegalStateException if no blank parameters
+     * @throws IllegalArgumentException if type doesn't match
+     */
+    Query<S> with(char value);
+
+    /**
+     * Returns a copy of this Query with the next blank parameter filled in.
+     *
+     * @param value parameter value to fill in
+     * @throws IllegalStateException if no blank parameters
+     * @throws IllegalArgumentException if type doesn't match
+     */
+    Query<S> with(byte value);
+
+    /**
+     * Returns a copy of this Query with the next blank parameter filled in.
+     *
+     * @param value parameter value to fill in
+     * @throws IllegalStateException if no blank parameters
+     * @throws IllegalArgumentException if type doesn't match
+     */
+    Query<S> with(short value);
+
+    /**
+     * Returns a copy of this Query with the next blank parameter filled in.
+     *
+     * @param value parameter value to fill in
+     * @throws IllegalStateException if no blank parameters
+     * @throws IllegalArgumentException if type doesn't match
+     */
+    Query<S> with(Object value);
+
+    /**
+     * Returns a copy of this Query with the next blank parameters filled in.
+     *
+     * @param values parameter values to fill in; if null or empty, this
+     * Query instance is returned
+     * @throws IllegalStateException if no blank parameters or if too many
+     * parameter values supplied
+     * @throws IllegalArgumentException if any type doesn't match
+     */
+    Query<S> withValues(Object... values);
+
+    /**
+     * Returns a new query which has another {@link Storage#query(String)
+     * filter} logically "and"ed to this, potentially reducing the amount of
+     * results.
+     *
+     * @param filter query filter expression
+     * @throws FetchException if storage layer throws an exception
+     * @throws IllegalStateException if any blank parameters in this query, or
+     * if this query is already guaranteed to fetch nothing
+     * @throws IllegalArgumentException if filter is null
+     * @throws MalformedFilterException if expression is malformed
+     * @throws UnsupportedOperationException if given filter is unsupported by repository
+     */
+    Query<S> and(String filter) throws FetchException;
+
+    /**
+     * Returns a new query which has another {@link Storage#query(String)
+     * filter} logically "and"ed to this, potentially reducing the amount of
+     * results.
+     *
+     * @param filter query filter
+     * @throws FetchException if storage layer throws an exception
+     * @throws IllegalStateException if any blank parameters in this query, or
+     * if this query is already guaranteed to fetch nothing
+     * @throws IllegalArgumentException if filter is null
+     * @throws UnsupportedOperationException if given filter is unsupported by repository
+     */
+    Query<S> and(Filter<S> filter) throws FetchException;
+
+    /**
+     * Returns a new query which has another {@link Storage#query(String)
+     * filter} logically "or"ed to this, potentially increasing the amount of
+     * results.
+     *
+     * @param filter query filter expression
+     * @throws FetchException if storage layer throws an exception
+     * @throws IllegalStateException if any blank parameters in this query, or
+     * if this query is already guaranteed to fetch everything
+     * @throws IllegalArgumentException if filter is null
+     * @throws MalformedFilterException if expression is malformed
+     * @throws UnsupportedOperationException if given filter is unsupported by repository
+     */
+    Query<S> or(String filter) throws FetchException;
+
+    /**
+     * Returns a new query which has another {@link Storage#query(String)
+     * filter} logically "or"ed to this, potentially increasing the amount of
+     * results.
+     *
+     * @param filter query filter
+     * @throws FetchException if storage layer throws an exception
+     * @throws IllegalStateException if any blank parameters in this query, or
+     * if this query is already guaranteed to fetch everything
+     * @throws IllegalArgumentException if filter is null
+     * @throws UnsupportedOperationException if given filter is unsupported by repository
+     */
+    Query<S> or(Filter<S> filter) throws FetchException;
+
+    /**
+     * Returns a new query which produces all the results not supplied in this
+     * query. Any filled in parameters in this query are copied into the new
+     * one.
+     *
+     * @throws FetchException if storage layer throws an exception
+     * @throws UnsupportedOperationException if new query is unsupported by repository
+     */
+    Query<S> not() throws FetchException;
+
+    /**
+     * Returns a copy of this query ordered by a specific property value. The
+     * property name may be prefixed with '+' or '-' to indicate ascending or
+     * descending order. If the prefix is omitted, ascending order is assumed.
+     *
+     * <p>Note: Specification of ordering properties is not cumulative. Calling
+     * this method will first remove any previous ordering properties.
+     *
+     * @param property name of property to order by
+     * @throws FetchException if storage layer throws an exception
+     * @throws IllegalArgumentException if property is null or is not a member
+     * of type S
+     * @throws UnsupportedOperationException if given ordering, combined with
+     * query filter, is unsupported by repository
+     */
+    Query<S> orderBy(String property) throws FetchException;
+
+    /**
+     * Returns a copy of this query ordered by specific property values. The
+     * property names may be prefixed with '+' or '-' to indicate ascending or
+     * descending order. If the prefix is omitted, ascending order is assumed.
+     *
+     * <p>Note: Specification of ordering properties is not cumulative. Calling
+     * this method will first remove any previous ordering properties.
+     *
+     * @param properties names of properties to order by
+     * @throws FetchException if storage layer throws an exception
+     * @throws IllegalArgumentException if any property is null or is not a
+     * member of type S
+     * @throws UnsupportedOperationException if given ordering, combined with
+     * query filter, is unsupported by repository
+     */
+    Query<S> orderBy(String... properties) throws FetchException;
+
+    /**
+     * Fetches results for this query. If any updates or deletes might be
+     * performed on the results, consider enclosing the fetch in a
+     * transaction. This allows the isolation level and "for update" mode to be
+     * adjusted. Some repositories might otherwise deadlock.
+     *
+     * @return fetch results
+     * @throws IllegalStateException if any blank parameters in this query
+     * @throws FetchException if storage layer throws an exception
+     * @see Repository#enterTransaction(IsolationLevel)
+     */
+    Cursor<S> fetch() throws FetchException;
+
+    /**
+     * Fetches results for this query after a given starting point, which is
+     * useful for re-opening a cursor. This is only effective when query has
+     * been given an explicit {@link #orderBy ordering}. If not a total
+     * ordering, then returned cursor may start at an earlier position.
+     *
+     * <p>Note: This method can be very expensive to call repeatedly, if the
+     * query needs to perform a sort operation. Ideally, the query ordering
+     * should match the natural ordering of an index or key.
+     *
+     * @param start storable to attempt to start after; if null, fetch all results
+     * @return fetch results
+     * @throws IllegalStateException if any blank parameters in this query
+     * @throws FetchException if storage layer throws an exception
+     * @see Repository#enterTransaction(IsolationLevel)
+     */
+    Cursor<S> fetchAfter(S start) throws FetchException;
+
+    /**
+     * Attempts to load exactly one matching object. If the number of matching
+     * records is zero or exceeds one, then an exception is thrown instead.
+     *
+     * @return a single fetched object
+     * @throws IllegalStateException if any blank parameters in this query
+     * @throws FetchNoneException if no matching record found
+     * @throws FetchMultipleException if more than one matching record found
+     * @throws FetchException if storage layer throws an exception
+     */
+    S loadOne() throws FetchException;
+
+    /**
+     * May return null if nothing found. Throws exception if record count is
+     * more than one.
+     *
+     * @return null or a single fetched object
+     * @throws IllegalStateException if any blank parameters in this query
+     * @throws FetchMultipleException if more than one matching record found
+     * @throws FetchException if storage layer throws an exception
+     */
+    S tryLoadOne() throws FetchException;
+
+    /**
+     * Deletes one matching object. If the number of matching records is zero or
+     * exceeds one, then no delete occurs, and an exception is thrown instead.
+     *
+     * @throws IllegalStateException if any blank parameters in this query
+     * @throws PersistNoneException if no matching record found
+     * @throws PersistMultipleException if more than one record matches
+     * @throws PersistException if storage layer throws an exception
+     */
+    void deleteOne() throws PersistException;
+
+    /**
+     * Deletes zero or one matching objects. If the number of matching records
+     * exceeds one, then no delete occurs, and an exception is thrown instead.
+     *
+     * @return true if record existed and was deleted, or false if no match
+     * @throws IllegalStateException if any blank parameters in this query
+     * @throws PersistMultipleException if more than one record matches
+     * @throws PersistException if storage layer throws an exception
+     */
+    boolean tryDeleteOne() throws PersistException;
+
+    /**
+     * Deletes zero or more matching objects. There is no guarantee that
+     * deleteAll is an atomic operation. If atomic behavior is desired, wrap
+     * the call in a transaction scope.
+     *
+     * @throws IllegalStateException if any blank parameters in this query
+     * @throws PersistException if storage layer throws an exception
+     */
+    void deleteAll() throws PersistException;
+
+    /**
+     * Returns a count of all results matched by this query. Even though no
+     * results are explicitly fetched, this method may still be expensive to
+     * call. The actual performance will vary by repository and available indexes.
+     *
+     * @return count of matches
+     * @throws IllegalStateException if any blank parameters in this query
+     * @throws FetchException if storage layer throws an exception
+     */
+    long count() throws FetchException;
+
+    /**
+     * Print the native query to standard out, which is useful for performance
+     * analysis. Not all repositories have a native query format. An example
+     * native format is SQL.
+     *
+     * @return false if not implemented
+     */
+    boolean printNative();
+
+    /**
+     * Prints the native query to any appendable, which is useful for
+     * performance analysis. Not all repositories have a native query
+     * format. An example native format is SQL.
+     *
+     * @param app append results here
+     * @return false if not implemented
+     */
+    boolean printNative(Appendable app) throws IOException;
+
+    /**
+     * Prints the native query to any appendable, which is useful for
+     * performance analysis. Not all repositories have a native query
+     * format. An example native format is SQL.
+     *
+     * @param app append results here
+     * @param indentLevel amount to indent text, zero for none
+     * @return false if not implemented
+     */
+    boolean printNative(Appendable app, int indentLevel) throws IOException;
+
+    /**
+     * Prints the query excecution plan to standard out, which is useful for
+     * performance analysis. There is no standard format for query plans, nor
+     * is it a requirement that this method be implemented.
+     *
+     * @return false if not implemented
+     */
+    boolean printPlan();
+
+    /**
+     * Prints the query excecution plan to any appendable, which is useful for
+     * performance analysis. There is no standard format for query plans, nor
+     * is it a requirement that this method be implemented.
+     *
+     * @param app append results here
+     * @return false if not implemented
+     */
+    boolean printPlan(Appendable app) throws IOException;
+
+    /**
+     * Prints the query excecution plan to any appendable, which is useful for
+     * performance analysis. There is no standard format for query plans, nor
+     * is it a requirement that this method be implemented.
+     *
+     * @param app append results here
+     * @param indentLevel amount to indent text, zero for none
+     * @return false if not implemented
+     */
+    boolean printPlan(Appendable app, int indentLevel) throws IOException;
+
+    int hashCode();
+
+    boolean equals(Object obj);
+
+    /**
+     * Returns a description of the query filter and any other arguments.
+     */
+    String toString();
+}
diff --git a/src/main/java/com/amazon/carbonado/Repository.java b/src/main/java/com/amazon/carbonado/Repository.java
new file mode 100644
index 0000000..7128fea
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/Repository.java
@@ -0,0 +1,157 @@
+/*
+ * 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 com.amazon.carbonado.capability.Capability;
+
+/**
+ * A Repository represents a database for {@link Storable}
+ * instances. Some repositories do not have control over the schema (for example, a JDBC
+ * Repository depends on the schema defined by the underlying relational database); such
+ * repositories are called "dependent".  Conversely, a repository which has complete control
+ * over the schema is termed "independent".
+ *
+ * <P>A dependent repository requires and will verify that Storables
+ * have a matching definition in the external storage layer. An independent
+ * repository will automatically update type definitions in its database to
+ * match changes to Storable definitions.
+ *
+ * <p>Repository instances should be thread-safe and immutable. Therefore, it
+ * is safe for multiple threads to be interacting with a Repository.
+ *
+ * @author Brian S O'Neill
+ * @see RepositoryBuilder
+ */
+public interface Repository {
+    /**
+     * Returns the name of this repository.
+     */
+    String getName();
+
+    /**
+     * Returns a Storage instance for the given user defined Storable class or
+     * interface.
+     *
+     * @return specific type of Storage instance
+     * @throws IllegalArgumentException if specified type is null
+     * @throws MalformedTypeException if specified type is not suitable
+     * @throws SupportException if specified type cannot be supported
+     * @throws RepositoryException if storage layer throws any other kind of
+     * exception
+     */
+    <S extends Storable> Storage<S> storageFor(Class<S> type)
+        throws SupportException, RepositoryException;
+
+    /**
+     * Causes the current thread to enter a transaction scope. Call commit
+     * inside the transaction in order for any updates to the repository to be
+     * applied. Be sure to call exit when leaving the scope.
+     * <p>
+     * To ensure exit is called, use transactions as follows:
+     * <pre>
+     * Transaction txn = repository.enterTransaction();
+     * try {
+     *     // Make updates to storage layer
+     *     ...
+     *
+     *     // Commit the changes up to this point
+     *     txn.commit();
+     *
+     *     // Optionally make more updates
+     *     ...
+     *
+     *     // Commit remaining changes
+     *     txn.commit();
+     * } finally {
+     *     // Ensure transaction exits, aborting uncommitted changes if an exception was thrown
+     *     txn.exit();
+     * }
+     * </pre>
+     */
+    Transaction enterTransaction();
+
+    /**
+     * Causes the current thread to enter a transaction scope with an explict
+     * isolation level. The actual isolation level may be higher than
+     * requested, if the repository does not support the exact level. If the
+     * repository does not support a high enough level, it throws an
+     * UnsupportedOperationException.
+     *
+     * @param level minimum desired transaction isolation level -- if null, a
+     * suitable default is selected
+     * @see #enterTransaction()
+     * @throws UnsupportedOperationException if repository does not support
+     * isolation as high as the desired level
+     */
+    Transaction enterTransaction(IsolationLevel level);
+
+    /**
+     * Causes the current thread to enter a <i>top-level</i> transaction scope
+     * with an explict isolation level. The actual isolation level may be
+     * higher than requested, if the repository does not support the exact
+     * level. If the repository does not support a high enough level, it throws
+     * an UnsupportedOperationException.
+     *
+     * <p>This method requests a top-level transaction, which means it never
+     * has a parent transaction, but it still can be a parent transaction
+     * itself. This kind of transaction is useful when a commit must absolutely
+     * succeed, even if the current thread is already in a transaction
+     * scope. If there was a parent transaction, then a commit might still be
+     * rolled back by the parent.
+     *
+     * <p>Requesting a top-level transaction can be deadlock prone if the
+     * current thread is already in a transaction scope. The top-level
+     * transaction may not be able to obtain locks held by the parent
+     * transaction. An alternative to requesting top-level transactions is to
+     * execute transactions in separate threads.
+     *
+     * @param level minimum desired transaction isolation level -- if null, a
+     * suitable default is selected
+     * @see #enterTransaction()
+     * @throws UnsupportedOperationException if repository does not support
+     * isolation as high as the desired level
+     */
+    Transaction enterTopTransaction(IsolationLevel level);
+
+    /**
+     * Returns the isolation level of the current transaction, or null if there
+     * is no transaction in the current thread.
+     */
+    IsolationLevel getTransactionIsolationLevel();
+
+    /**
+     * Requests a specific capability of this Repository. This allows
+     * repositories to support extended features without having to clutter the
+     * main repository interface. The list of supported capabilities is
+     * documented with repository implementations.
+     *
+     * @param capabilityType type of capability requested
+     * @return capability instance or null if not supported
+     */
+    <C extends Capability> C getCapability(Class<C> capabilityType);
+
+    /**
+     * Closes this repository reference, aborting any current
+     * transactions. Operations on objects returned by this repository will
+     * fail when accessing the storage layer.
+     *
+     * @throws SecurityException if caller does not have permission
+     */
+    void close();
+}
diff --git a/src/main/java/com/amazon/carbonado/RepositoryBuilder.java b/src/main/java/com/amazon/carbonado/RepositoryBuilder.java
new file mode 100644
index 0000000..4b1d3fe
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/RepositoryBuilder.java
@@ -0,0 +1,104 @@
+/*
+ * 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.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Standard interface for building up configuration and opening a {@link
+ * Repository} instance. All repository implementations should be constructable
+ * via a builder that implements this interface. Builders should follow a
+ * pattern where configuration is supplied via property access methods. With
+ * this design, each item can have extensive documentation and optional
+ * configuration can be ignored.
+ *
+ * <p>A builder design also offers advantages over constructors in that a
+ * different repository can be built depending on the specific
+ * configuration. This logic is hidden, making it easier to use repositories
+ * that would otherwise require complex steps to construct.
+ *
+ * @author Brian S O'Neill
+ */
+public interface RepositoryBuilder {
+    /**
+     * Builds a repository instance.
+     *
+     * @throws ConfigurationException if there is a problem in the builder's configuration
+     * @throws RepositoryException if there is a general problem opening the repository
+     */
+    Repository build() throws ConfigurationException, RepositoryException;
+
+    /**
+     * Builds a repository instance.
+     *
+     * <p>If the repository is being wrapped by a parent repository, the child
+     * repository will need to know this fact for some operations to work
+     * correctly. Since the parent repository is not built yet, a reference is
+     * used instead.
+     *
+     * @param rootReference reference to root parent repository, to be set by
+     * parent repository upon being built
+     * @throws ConfigurationException if there is a problem in the builder's configuration
+     * @throws RepositoryException if there is a general problem opening the repository
+     */
+    Repository build(RepositoryReference rootReference)
+        throws ConfigurationException, RepositoryException;
+
+    /**
+     * Returns the name of the repository.
+     */
+    String getName();
+
+    /**
+     * Set name for the repository, which is required.
+     */
+    void setName(String name);
+
+    /**
+     * Returns true if repository should assume the role of master, which is
+     * true by default. Repositories that link different repositories together
+     * will designate only one as the master.
+     *
+     * <p>A master repository is responsible for {@link Version version} and
+     * {@link Sequence sequence} properties. For insert operations, a master
+     * repository must set these properties if they are uninitialized. For
+     * updates, the version property is checked to see if an {@link
+     * OptimisticLockException} should be thrown.
+     *
+     * @see com.amazon.carbonado.repo.replicated.ReplicatedRepositoryBuilder
+     */
+    boolean isMaster();
+
+    /**
+     * Set to false if repository should not assume the role of master. By
+     * default, this option is true. Repositories that link different
+     * repositories together will designate only one as the master.
+     *
+     * <p>A master repository is responsible for {@link Version version} and
+     * {@link Sequence sequence} properties. For insert operations, a master
+     * repository must set these properties if they are uninitialized. For
+     * updates, the version property is checked to see if an {@link
+     * OptimisticLockException} should be thrown.
+     *
+     * @see com.amazon.carbonado.repo.replicated.ReplicatedRepositoryBuilder
+     */
+    void setMaster(boolean b);
+
+    public class RepositoryReference extends AtomicReference<Repository> {}
+}
diff --git a/src/main/java/com/amazon/carbonado/RepositoryException.java b/src/main/java/com/amazon/carbonado/RepositoryException.java
new file mode 100644
index 0000000..e6887b6
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/RepositoryException.java
@@ -0,0 +1,220 @@
+/*
+ * 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.util.Random;
+
+/**
+ * General checked exception thrown when accessing a {@link Repository}.
+ *
+ * <p>Some repository exceptions are the result of an optimistic lock failure
+ * or deadlock. One resolution strategy is to exit all transactions and try the
+ * operation again, after waiting some bounded random amount of time. As a
+ * convenience, this class provides a mechanism to support such a backoff
+ * strategy. For example:
+ *
+ * <pre>
+ * // Retry at most three more times
+ * for (int retryCount = 3;;) {
+ *     try {
+ *         ...
+ *         myObject.load();
+ *         ...
+ *         myObject.update();
+ *         break;
+ *     } catch (OptimisticLockException e) {
+ *         // Wait up to one second before retrying
+ *         retryCount = e.backoff(e, retryCount, 1000);
+ *     }
+ * }
+ * </pre>
+ *
+ * If the retry count is zero (or less) when backoff is called, then the
+ * original exception is rethrown, indicating retry failure.
+ *
+ * @author Brian S O'Neill
+ */
+public class RepositoryException extends Exception {
+
+    private static final long serialVersionUID = 7261406895435249366L;
+
+    /**
+     * One strategy for resolving an optimistic lock failure is to try the
+     * operation again, after waiting some bounded random amount of time. This
+     * method is provided as a convenience, to support such a random wait.
+     * <p>
+     * A retry count is required as well, which is decremented and returned by
+     * this method. If the retry count is zero (or less) when this method is
+     * called, then this exception is thrown again, indicating retry failure.
+     *
+     * @param retryCount current retry count, if zero, throw this exception again
+     * @param milliseconds upper bound on the random amount of time to wait
+     * @return retryCount minus one
+     * @throws E if retry count is zero
+     */
+    public static <E extends Throwable> int backoff(E e, int retryCount, int milliseconds)
+        throws E
+    {
+        if (retryCount <= 0) {
+            // Workaround apparent compiler bug.
+            com.amazon.carbonado.util.ThrowUnchecked.fire(e);
+        }
+        if (milliseconds > 0) {
+            Random rnd = cRandom;
+            if (rnd == null) {
+                cRandom = rnd = new Random();
+            }
+            if ((milliseconds = rnd.nextInt(milliseconds)) > 0) {
+                try {
+                    Thread.sleep(milliseconds);
+                } catch (InterruptedException e2) {
+                }
+                return retryCount - 1;
+            }
+        }
+        Thread.yield();
+        return retryCount - 1;
+    }
+
+    private static Random cRandom;
+
+    public RepositoryException() {
+        super();
+    }
+
+    public RepositoryException(String message) {
+        super(message);
+    }
+
+    public RepositoryException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public RepositoryException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Recursively calls getCause, until the root cause is found. Returns this
+     * if no root cause.
+     */
+    public Throwable getRootCause() {
+        Throwable cause = this;
+        while (cause.getCause() != null) {
+            cause = cause.getCause();
+        }
+        return cause;
+    }
+
+    /**
+     * Converts RepositoryException into an appropriate PersistException.
+     */
+    public final PersistException toPersistException() {
+        return toPersistException(null);
+    }
+
+    /**
+     * Converts RepositoryException into an appropriate PersistException, prepending
+     * the specified message. If message is null, original exception message is
+     * preserved.
+     *
+     * @param message message to prepend, which may be null
+     */
+    public final PersistException toPersistException(final String message) {
+        Throwable cause;
+        if (this instanceof PersistException) {
+            cause = this;
+        } else {
+            cause = getCause();
+        }
+
+        if (cause == null) {
+            cause = this;
+        } else if (cause instanceof PersistException && message == null) {
+            return (PersistException) cause;
+        }
+
+        String causeMessage = cause.getMessage();
+        if (causeMessage == null) {
+            causeMessage = message;
+        } else if (message != null) {
+            causeMessage = message + " : " + causeMessage;
+        }
+
+        return makePersistException(causeMessage, cause);
+    }
+
+    /**
+     * Converts RepositoryException into an appropriate FetchException.
+     */
+    public final FetchException toFetchException() {
+        return toFetchException(null);
+    }
+
+    /**
+     * Converts RepositoryException into an appropriate FetchException, prepending
+     * the specified message. If message is null, original exception message is
+     * preserved.
+     *
+     * @param message message to prepend, which may be null
+     */
+    public final FetchException toFetchException(final String message) {
+        Throwable cause;
+        if (this instanceof FetchException) {
+            cause = this;
+        } else {
+            cause = getCause();
+        }
+
+        if (cause == null) {
+            cause = this;
+        } else if (cause instanceof FetchException && message == null) {
+            return (FetchException) cause;
+        }
+
+        String causeMessage = cause.getMessage();
+        if (causeMessage == null) {
+            causeMessage = message;
+        } else if (message != null) {
+            causeMessage = message + " : " + causeMessage;
+        }
+
+        return makeFetchException(causeMessage, cause);
+    }
+
+    /**
+     * Subclasses can override this to provide a more specialized exception.
+     *
+     * @param message exception message, which may be null
+     * @param cause non-null cause
+     */
+    protected PersistException makePersistException(String message, Throwable cause) {
+        return new PersistException(message, cause);
+    }
+
+    /**
+     * Subclasses can override this to provide a more specialized exception.
+     *
+     * @param message exception message, which may be null
+     * @param cause non-null cause
+     */
+    protected FetchException makeFetchException(String message, Throwable cause) {
+        return new FetchException(message, cause);
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/Sequence.java b/src/main/java/com/amazon/carbonado/Sequence.java
new file mode 100644
index 0000000..ea38493
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/Sequence.java
@@ -0,0 +1,49 @@
+/*
+ * 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, by a named sequence. Sequences are supported at the storage
+ * layer, and implementions that do not support sequences ignore this
+ * annotation.
+ *
+ * <p>Example:<pre>
+ * &#64;PrimaryKey("userInfoID")
+ * public interface UserInfo extends Storable&lt;UserInfo&gt; {
+ *     <b>&#64;Sequence("USER_ID_SEQ")</b>
+ *     long getUserInfoID();
+ *
+ *     ...
+ * }
+ * </pre>
+ *
+ * @author Brian S O'Neill
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD})
+public @interface Sequence {
+    /**
+     * Name of the sequence used by the storage layer.
+     */
+    String value();
+}
diff --git a/src/main/java/com/amazon/carbonado/Storable.java b/src/main/java/com/amazon/carbonado/Storable.java
new file mode 100644
index 0000000..f5c2248
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/Storable.java
@@ -0,0 +1,424 @@
+/*
+ * 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;
+
+/**
+ * A data access object in a {@link Repository}. User defined storables must
+ * either extend or implement this interface via an interface or abstract
+ * class. Bean properties defined in the storable are persisted into the
+ * repository. At least one property must be annotated as the {@link
+ * PrimaryKey}. At most one property may be annotated as being the {@link
+ * Version} property.
+ *
+ * <p>Storable instances are mutable, but they must be thread-safe. Although
+ * there may be race conditions if multiple threads are mutating the Storable,
+ * the Storable instance will not get into a corrupt state.
+ *
+ * @author Brian S O'Neill
+ * @author Don Schneider
+ *
+ * @see com.amazon.carbonado.Alias
+ * @see com.amazon.carbonado.Indexes
+ * @see com.amazon.carbonado.Join
+ * @see com.amazon.carbonado.Nullable
+ * @see com.amazon.carbonado.PrimaryKey
+ * @see com.amazon.carbonado.Version
+ */
+public interface Storable<S extends Storable<S>> {
+    /**
+     * Loads or reloads this object from the storage layer by its primary
+     * key. This object's primary key itself is never modified by calling
+     * load. If load is successful, altering the primary key is no longer
+     * allowed unless the object is deleted. Attempting to alter the primary
+     * key in this state results in an {@link IllegalStateException}.
+     *
+     * <p>Note: This method differs from {@link #tryLoad} only in that it
+     * throws an exception if no matching record was found instead of returning
+     * false. This may indicate that the underlying record was deleted between
+     * a load and reload. When a FetchNoneException is thrown, this object's
+     * state will be the same as if the delete method was called on it.
+     *
+     * @throws FetchNoneException if no matching record found
+     * @throws FetchException if storage layer throws an exception
+     * @throws IllegalStateException if the state of this instance suggests
+     * that any primary keys are unspecified
+     */
+    void load() throws FetchNoneException, FetchException;
+
+    /**
+     * Loads or reloads this object from the storage layer by its primary
+     * key. This object's primary key itself is never modified by calling
+     * load. If load is successful, altering the primary key is no longer
+     * allowed unless the object is deleted. Attempting to alter the primary
+     * key in this state results in an {@link IllegalStateException}.
+     *
+     * <p>Note: This method differs from {@link #load} only in that it returns
+     * false if no matching record was found instead of throwing an exception.
+     * This may indicate that the underlying record was deleted between a load
+     * and reload. When false is returned, this object's state will be the same
+     * as if the delete method was called on it.
+     *
+     * @return true if found and loaded, false otherwise
+     * @throws FetchException if storage layer throws an exception
+     * @throws IllegalStateException if the state of this instance suggests
+     * that any primary keys are unspecified
+     */
+    boolean tryLoad() throws FetchException;
+
+    /**
+     * Inserts a new persistent value for this object. If successful, altering
+     * the primary key is no longer allowed unless the object is deleted.
+     * Attempting to alter the primary key in this state results in an
+     * {@link IllegalStateException}.
+     *
+     * <p>Insert requires that all primary key properties be specified. If not,
+     * an {@link IllegalStateException} is thrown. Also, repository
+     * implementations usually require that properties which are not {@link
+     * Nullable} also be specified. Otherwise, a {@link ConstraintException}
+     * may be thrown.
+     *
+     * <p>Note: This method differs from {@link #tryInsert} only in that it may
+     * throw a UniqueConstraintException instead of returning false.
+     *
+     * @throws UniqueConstraintException if it is absolutely known that a key
+     * of inserted object matches an existing one
+     * @throws ConstraintException if any required properties are unspecified
+     * @throws PersistException if storage layer throws an exception
+     * @throws IllegalStateException if the state of this instance suggests
+     * that any primary keys are unspecified
+     */
+    void insert() throws PersistException;
+
+    /**
+     * Inserts a new persistent value for this object. If successful, altering
+     * the primary key is no longer allowed unless the object is deleted.
+     * Attempting to alter the primary key in this state results in an
+     * {@link IllegalStateException}.
+     *
+     * <p>Insert requires that all primary key properties be specified. If not,
+     * an {@link IllegalStateException} is thrown. Also, repository
+     * implementations usually require that properties which are not {@link
+     * Nullable} also be specified. Otherwise, a {@link ConstraintException}
+     * may be thrown.
+     *
+     * <p>Note: This method differs from {@link #insert} only in that it
+     * returns false instead of throwing a UniqueConstraintException.
+     *
+     * @return false if it is absolutely known that a key of inserted object
+     * matches an existing one
+     * @throws ConstraintException if any required properties are unspecified
+     * @throws PersistException if storage layer throws an exception
+     * @throws IllegalStateException if the state of this instance suggests
+     * that any primary keys are unspecified
+     */
+    boolean tryInsert() throws PersistException;
+
+    /**
+     * Updates the persistent value of this object, regardless of whether this
+     * object has actually been loaded or not. If successful, altering the
+     * primary key is no longer allowed unless the object is deleted.
+     * Attempting to alter the primary key in this state results in an
+     * {@link IllegalStateException}.
+     *
+     * <p>If this object has a {@link Version version} property defined, then
+     * the update logic is a bit more strict. Updates of any storable require
+     * that the primary keys be specified; if a version is present, the version
+     * must be specified as well. If any of the primary key or version
+     * properties are unspecified, an {@link IllegalStateException} will be
+     * thrown; if they are fully specified and the version doesn't match the
+     * current record, an {@link OptimisticLockException} is thrown.
+     *
+     * <p>Not all properties need to be set on this object when calling
+     * update. Setting a subset results in a partial update. After a successful
+     * update, all properties are set to the actual values in the storage
+     * layer. Put another way, the object is automatically reloaded after a
+     * successful update.
+     *
+     * <p>If PersistNoneException is thrown, this indicates that the underlying
+     * record was deleted. When this happens, this object's state will be the
+     * same as if the delete method was called on it.
+     *
+     * @throws PersistNoneException if record is missing and no update occurred
+     * @throws PersistException if storage layer throws an exception
+     * @throws OptimisticLockException if a version property exists and the
+     * optimistic lock failed
+     * @throws IllegalStateException if the state of this instance suggests
+     * that any primary keys are unspecified, or if a version property is unspecified
+     */
+    void update() throws PersistException;
+
+    /**
+     * Updates the persistent value of this object, regardless of whether this
+     * object has actually been loaded or not. If successful, altering the
+     * primary key is no longer allowed unless the object is deleted.
+     * Attempting to alter the primary key in this state results in an
+     * {@link IllegalStateException}.
+     *
+     * <p>If this object has a {@link Version version} property defined, then
+     * the update logic is a bit more strict. Updates of any storable require
+     * that the primary keys be specified; if a version is present, the version
+     * must be specified as well. If any of the primary key or version
+     * properties are unspecified, an {@link IllegalStateException} will be
+     * thrown; if they are fully specified and the version doesn't match the
+     * current record, an {@link OptimisticLockException} is thrown.
+     *
+     * <p>Not all properties need to be set on this object when calling
+     * update. Setting a subset results in a partial update. After a successful
+     * update, all properties are set to the actual values in the storage
+     * layer. Put another way, the object is automatically reloaded after a
+     * successful update.
+     *
+     * <p>A return value of false indicates that the underlying record was
+     * deleted. When this happens, this object's state will be the same as if
+     * the delete method was called on it.
+     *
+     * @return true if record likely exists and was updated, or false if record
+     * absolutely no longer exists and no update occurred
+     * @throws PersistException if storage layer throws an exception
+     * @throws OptimisticLockException if a version property exists and the
+     * optimistic lock failed
+     * @throws IllegalStateException if the state of this instance suggests
+     * that any primary keys are unspecified, or if a version property is unspecified
+     */
+    boolean tryUpdate() throws PersistException;
+
+    /**
+     * Deletes this object from the storage layer by its primary key,
+     * regardless of whether this object has actually been loaded or not.
+     * Calling delete does not prevent this object from being used again. All
+     * property values are still valid, including the primary key. Once
+     * deleted, the insert operation is permitted again.
+     *
+     * <p>Note: This method differs from {@link #tryDelete} only in that it may
+     * throw a PersistNoneException instead of returning false.
+     *
+     * @throws PersistNoneException if record is missing and nothing was
+     * deleted
+     * @throws PersistException if storage layer throws an exception
+     * @throws IllegalStateException if the state of this instance suggests
+     * that any primary keys are unspecified
+     */
+    void delete() throws PersistException;
+
+    /**
+     * Deletes this object from the storage layer by its primary key,
+     * regardless of whether this object has actually been loaded or not.
+     * Calling delete does not prevent this object from being used again. All
+     * property values are still valid, including the primary key. Once
+     * deleted, the insert operation is permitted again.
+     *
+     * <p>Note: This method differs from {@link #delete} only in that it
+     * returns false instead of throwing a PersistNoneException.
+     *
+     * @return true if record likely existed and was deleted, or false if record
+     * absolutely no longer exists and no delete was necessary
+     * @throws PersistException if storage layer throws an exception
+     * @throws IllegalStateException if the state of this instance suggests
+     * that any primary keys are unspecified
+     */
+    boolean tryDelete() throws PersistException;
+
+    /**
+     * Returns the class or interface from which this storable was
+     * generated. This represents the data class for the storable.
+     *
+     * <p><i>Design note: the name "getStorableType" is avoided, so as not to
+     * conflict with a user defined property of "storableType"</i>
+     */
+    Class<S> storableType();
+
+    /**
+     * Copies all supported properties, skipping any that are uninitialized.
+     * Specifically, calls "target.set&lt;property&gt;" for all supported
+     * properties in this storable, passing the value of the property from this
+     * object. Unsupported {@link Independent independent} properties in this
+     * or the target are not copied.
+     *
+     * @param target storable on which to call set&lt;property&gt; methods
+     * @throws IllegalStateException if any primary key properties of target
+     * cannot be altered
+     */
+    void copyAllProperties(S target);
+
+    /**
+     * Copies all supported primary key properties, skipping any that are
+     * uninitialized. Specifically, calls "target.set&lt;property&gt;" for all
+     * supported properties which participate in the primary key, passing the
+     * value of the property from this object. Unsupported {@link Independent
+     * independent} properties in this or the target are not copied.
+     *
+     * @param target storable on which to call set&lt;property&gt; methods
+     * @throws IllegalStateException if any primary key properties of target
+     * cannot be altered
+     */
+    void copyPrimaryKeyProperties(S target);
+
+    /**
+     * Copies the optional version property, unless it is uninitialized.
+     * Specifically, calls "target.set&lt;property&gt;" for the version
+     * property (if supported), passing the value of the property from this
+     * object. If no version property is defined, then this method does
+     * nothing. Unsupported {@link Independent independent} properties in this
+     * or the target are not copied.
+     *
+     * @param target storable on which to call set&lt;property&gt; method
+     */
+    void copyVersionProperty(S target);
+
+    /**
+     * Copies all supported non-primary key properties which are unequal,
+     * skipping any that are uninitialized. Specifically, calls
+     * "target.get&lt;property&gt;", and if the value thus retrieved differs
+     * from the local value, "target.set&lt;property&gt;" is called for that
+     * property. Unsupported {@link Independent independent} properties in this
+     * or the target are not copied.
+     *
+     * @param target storable on which to call set&lt;property&gt; methods
+     */
+    void copyUnequalProperties(S target);
+
+    /**
+     * Copies all supported non-primary key properties which are
+     * dirty. Specifically, calls "target.set&lt;property&gt;" for any
+     * non-primary key property which is dirty, passing the value of the
+     * property from this object. A property is considered dirty when set
+     * before a load or persist operation is called. Unsupported {@link
+     * Independent independent} properties in this or the target are not
+     * copied.
+     *
+     * @param target storable on which to call set&lt;property&gt; methods
+     */
+    void copyDirtyProperties(S target);
+
+    /**
+     * Returns true if any non-primary key properties in this object are
+     * dirty. A property is considered dirty when set before a load or persist
+     * operation is called. A property becomes clean after a successful load,
+     * insert, or update operation.
+     */
+    boolean hasDirtyProperties();
+
+    /**
+     * Marks all dirty properties as clean. Uninitialized properties remain so.
+     * As a side-effect, initialized primary keys may no longer be altered.
+     */
+    void markPropertiesClean();
+
+    /**
+     * Marks all properties as clean, including uninitialized properties.
+     * As a side-effect, primary keys may no longer be altered.
+     */
+    void markAllPropertiesClean();
+
+    /**
+     * Marks all clean properties as dirty. Uninitialized properties remain so.
+     * As a side-effect, primary keys can be altered.
+     */
+    void markPropertiesDirty();
+
+    /**
+     * Marks all properties as dirty, including uninitialized properties.
+     * As a side-effect, primary keys can be altered.
+     */
+    void markAllPropertiesDirty();
+
+    /**
+     * Returns true if the given property of this Storable has never been
+     * loaded or set.
+     *
+     * @param propertyName name of property to interrogate
+     * @throws IllegalArgumentException if property is unknown or is a join
+     */
+    boolean isPropertyUninitialized(String propertyName);
+
+    /**
+     * Returns true if the given property of this Storable has been set, but no
+     * load or store operation has been performed yet.
+     *
+     * @param propertyName name of property to interrogate
+     * @throws IllegalArgumentException if property is unknown or is a join
+     */
+    boolean isPropertyDirty(String propertyName);
+
+    /**
+     * Returns true if the given property of this Storable is clean. All
+     * properties are clean after a successful load or store operation.
+     *
+     * @param propertyName name of property to interrogate
+     * @throws IllegalArgumentException if property is unknown or is a join
+     */
+    boolean isPropertyClean(String propertyName);
+
+    /**
+     * Returns true if the given property exists and is supported. If a
+     * Storable has an {@link Independent} property which is not supported by
+     * the repository, then this method returns false.
+     *
+     * @param propertyName name of property to check
+     */
+    boolean isPropertySupported(String propertyName);
+
+    /**
+     * Returns an exact shallow copy of this object, including the state.
+     */
+    S copy();
+
+    int hashCode();
+
+    /**
+     * True if all properties and fields are equal, but ignoring the state.
+     *
+     * @param obj object to compare to for equality
+     */
+    boolean equals(Object obj);
+
+    /**
+     * True if the supported properties which participate in the primary key
+     * are equal. This is useful to cheaply investigate if two storables refer
+     * to the same entity, regardless of the state of object (specifically the
+     * non-key properties). Unsupported {@link Independent independent}
+     * properties in this or the target are not compared.
+     *
+     * @param obj object to compare to for equality
+     */
+    boolean equalPrimaryKeys(Object obj);
+
+    /**
+     * True if all supported properties for this object are equal. Unsupported
+     * {@link Independent independent} properties in this or the target are not
+     * compared.
+     *
+     * @param obj object to compare to for equality
+     */
+    boolean equalProperties(Object obj);
+
+    /**
+     * Returns a string for debugging purposes that contains all supported
+     * property names and values for this object. Unsupported {@link
+     * Independent independent} properties are not included.
+     */
+    String toString();
+
+    /**
+     * Returns a string for debugging purposes that contains only supported
+     * primary key property names and values for this object. Unsupported
+     * {@link Independent independent} properties are not included.
+     */
+    String toStringKeyOnly();
+}
diff --git a/src/main/java/com/amazon/carbonado/Storage.java b/src/main/java/com/amazon/carbonado/Storage.java
new file mode 100644
index 0000000..3489d63
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/Storage.java
@@ -0,0 +1,133 @@
+/*
+ * 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 com.amazon.carbonado.filter.Filter;
+
+/**
+ * Access for a specific type of {@link Storable} from a {@link Repository}.
+ *
+ * <p>Storage instances are thread-safe and immutable.
+ *
+ * @author Brian S O'Neill
+ */
+public interface Storage<S extends Storable> {
+    /**
+     * Returns the specific type of Storable managed by this object.
+     */
+    Class<S> getStorableType();
+
+    /**
+     * Prepares a new object for loading, inserting, updating, or deleting.
+     *
+     * @return a new data access object
+     */
+    S prepare();
+
+    /**
+     * Query for all Storable instances in this Storage.
+     *
+     * @see #query(String)
+     * @throws FetchException if storage layer throws an exception
+     */
+    Query<S> query() throws FetchException;
+
+    /**
+     * Query for Storable instances against a filter expression. A filter tests
+     * if property values match against specific values specified by '?'
+     * placeholders. The simplest filter compares just one property, like
+     * {@code "ID = ?"}. Filters can also contain several kinds of relational
+     * operators, boolean logic operators, sub-properties, and parentheses. A
+     * more complex example might be {@code "income < ? | (name = ? & address.zipCode != ?)"}.
+     * <p>
+     * When querying for a single Storable instance by its primary key, it is
+     * generally more efficient to call {@link #prepare()}, set primary key
+     * properties, and then call {@link Storable#load()}. For example, consider
+     * an object with a primary key consisting only of the property "ID". It
+     * can be queried as:
+     * <pre>
+     * Storage&lt;UserInfo&gt; users;
+     * UserInfo user = users.query("ID = ?").with(123456).loadOne();
+     * </pre>
+     * The above code will likely open a Cursor in order to verify that just
+     * one object was loaded. Instead, do this:
+     * <pre>
+     * Storage&lt;UserInfo&gt; users;
+     * UserInfo user = users.prepare();
+     * user.setID(123456);
+     * user.load();
+     * </pre>
+     * The complete syntax for query filters follows. Note that:
+     * <ul>
+     * <li> literals are not allowed
+     * <li> logical 'and' operator has precedence over 'or'
+     * <li> logical 'not' operator has precedence over 'and'
+     * <li> '?' placeholders can only appear after relational operators
+     * </ul>
+     * <pre>
+     * Filter          = OrFilter
+     * OrFilter        = AndFilter { "|" AndFilter }
+     * AndFilter       = NotFilter { "&" NotFilter }
+     * NotFilter       = [ "!" ] EntityFilter
+     * EntityFilter    = PropertyFilter
+     *                 | "(" Filter ")"
+     * PropertyFilter  = ChainedProperty RelOp "?"
+     * RelOp           = "=" | "!=" | "&lt;" | "&gt;=" | "&gt;" | "&lt;="
+     * ChainedProperty = Identifier { "." Identifier }
+     * </pre>
+     *
+     * @param filter query filter expression
+     * @throws FetchException if storage layer throws an exception
+     * @throws IllegalArgumentException if filter is null
+     * @throws MalformedFilterException if expression is malformed
+     * @throws UnsupportedOperationException if given filter is unsupported by repository
+     */
+    Query<S> query(String filter) throws FetchException;
+
+    /**
+     * Query for Storable instances against an explicitly constructed filter
+     * object.
+     *
+     * @param filter query filter
+     * @throws FetchException if storage layer throws an exception
+     * @throws IllegalArgumentException if filter is null
+     * @throws UnsupportedOperationException if given filter is unsupported by repository
+     */
+    Query<S> query(Filter<S> filter) throws FetchException;
+
+    /**
+     * 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
+     * nesting level.
+     *
+     * @return true if trigger was added, false if trigger was not added
+     * because an equal trigger is already registered
+     * @throws IllegalArgumentException if trigger is null
+     */
+    boolean addTrigger(Trigger<? super S> trigger);
+
+    /**
+     * Remove a trigger which was registered earlier.
+     *
+     * @return true if trigger instance was removed, false if not registered
+     * @throws IllegalArgumentException if trigger is null
+     */
+    boolean removeTrigger(Trigger<? super S> trigger);
+}
diff --git a/src/main/java/com/amazon/carbonado/SupportException.java b/src/main/java/com/amazon/carbonado/SupportException.java
new file mode 100644
index 0000000..cd9a835
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/SupportException.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+/**
+ * Indicates that an action cannot be supported by the {@link
+ * Repository} it is being requested from.  Typically this results from
+ * an attempt to get storage for a {@link Storable} which is not supported,
+ * or an attempt to configure a repository improperly.
+ *
+ * @author Brian S O'Neill
+ */
+public class SupportException extends RepositoryException {
+
+    private static final long serialVersionUID = -6150578915717928592L;
+
+    public SupportException() {
+        super();
+    }
+
+    public SupportException(String message) {
+        super(message);
+    }
+
+    public SupportException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public SupportException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/Transaction.java b/src/main/java/com/amazon/carbonado/Transaction.java
new file mode 100644
index 0000000..5c8203f
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/Transaction.java
@@ -0,0 +1,120 @@
+/*
+ * 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.util.concurrent.TimeUnit;
+
+/**
+ * Transactions define atomic operations which can be committed or aborted as a
+ * unit. Transactions are entered by calling {@link Repository#enterTransaction()}.
+ * Transactions are thread-local, and so no special action needs to be taken to
+ * bind operations to them. Cursors which are opened in the scope of a
+ * transaction are automatically closed when the transaction is committed or
+ * aborted.
+ *
+ * <p>Transactions do not exit when they are committed. The transaction is
+ * still valid after a commit, but new operations are grouped into a separate
+ * atomic unit. The exit method <em>must</em> be invoked on every
+ * transaction. The following pattern is recommended:
+ *
+ * <pre>
+ * Transaction txn = repository.enterTransaction();
+ * try {
+ *     // Make updates to storage layer
+ *     ...
+ *
+ *     // Commit the changes up to this point
+ *     txn.commit();
+ *
+ *     // Optionally make more updates
+ *     ...
+ *
+ *     // Commit remaining changes
+ *     txn.commit();
+ * } finally {
+ *     // Ensure transaction exits, aborting uncommitted changes if an exception was thrown
+ *     txn.exit();
+ * }
+ * </pre>
+ *
+ * <p>Transactions may be nested. Calling commit or abort on an outer
+ * transaction will recursively apply the same operation to all inner
+ * transactions as well. All Cursors contained within are also closed.
+ *
+ * <p>Transaction instances are mutable, but they are thread-safe.
+ *
+ * @author Brian S O'Neill
+ */
+public interface Transaction {
+    /**
+     * If currently in a transaction, commits all changes to the storage layer
+     * since the last commit within the transaction.
+     *
+     * @throws PersistException if storage layer throws an exception
+     */
+    void commit() throws PersistException;
+
+    /**
+     * Closes the current transaction, aborting all changes since the last
+     * commit.
+     *
+     * @throws PersistException if storage layer throws an exception
+     */
+    void exit() throws PersistException;
+
+    /**
+     * Set to true to force all read operations within this transaction to
+     * acquire upgradable or write locks. This option eliminates deadlocks that
+     * may occur when updating records, except it may increase contention.
+     */
+    void setForUpdate(boolean forUpdate);
+
+    /**
+     * Returns true if this transaction is in update mode, which is adjusted by
+     * calling {@link #setForUpdate}.
+     */
+    boolean isForUpdate();
+
+    /**
+     * Specify a desired timeout for aquiring locks within this
+     * transaction. Calling this method may have have no effect at all, if the
+     * repository does not support this feature. In addition, the lock timeout
+     * might not be alterable if the transaction contains uncommitted data.
+     *
+     * <p>Also, the range of lock timeout values supported might be small. For
+     * example, only a timeout value of zero might be supported. In that case,
+     * the transaction is configured to not wait at all when trying to acquire
+     * locks. Expect immediate timeout exceptions when locks cannot be
+     * granted.
+     *
+     * <p>Nested transactions inherit the desired lock timeout of their
+     * parent. Top transactions always begin with the default lock timeout.
+     *
+     * @param timeout Desired lock timeout. If negative, revert lock timeout to
+     * default value.
+     * @param unit Time unit for timeout. If null, revert lock timeout to
+     * default value.
+     */
+    void setDesiredLockTimeout(int timeout, TimeUnit unit);
+
+    /**
+     * Returns the isolation level of this transaction.
+     */
+    IsolationLevel getIsolationLevel();
+}
diff --git a/src/main/java/com/amazon/carbonado/Trigger.java b/src/main/java/com/amazon/carbonado/Trigger.java
new file mode 100644
index 0000000..20b042f
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/Trigger.java
@@ -0,0 +1,327 @@
+/*
+ * 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;
+
+/**
+ * Callback mechanism to allow custom code to run when a storable is
+ * persisted. By default, the methods defined in this class do
+ * nothing. Subclass and override trigger conditions of interest, and then
+ * {@link Storage#addTrigger register} it. Each overridden trigger method is
+ * called in the same transaction scope as the persist operation.
+ *
+ * <p>To ensure proper nesting, all "before" events are run in the
+ * <em>opposite</em> order that the trigger was registered. All "after" and
+ * "failed" events are run in the same order that the trigger was registered.
+ * In other words, the last added trigger is at the outermost nesting level.
+ *
+ * @author Brian S O'Neill
+ */
+public abstract class Trigger<S> {
+    /**
+     * Called before a storable is to be inserted. The default implementation
+     * does nothing.
+     *
+     * <p>Any exception thrown by this method will cause the insert operation
+     * to rollback and all remaining triggers to not run. The exception is
+     * ultimately passed to the caller of the insert method.
+     *
+     * @param storable storable before being inserted
+     * @return arbitrary state object, passed to afterInsert or failedInsert method
+     */
+    public Object beforeInsert(S storable) throws PersistException {
+        return null;
+    }
+
+    /**
+     * Called before a storable is to be inserted via tryInsert. The default
+     * implementation simply calls {@link #beforeInsert}. Only override if
+     * trigger needs to distinguish between different insert variants.
+     *
+     * <p>Any exception thrown by this method will cause the tryInsert operation
+     * to rollback and all remaining triggers to not run. The exception is
+     * ultimately passed to the caller of the tryInsert method.
+     *
+     * @param storable storable before being inserted
+     * @return arbitrary state object, passed to afterTryInsert or failedInsert method
+     * @see #abortTry
+     */
+    public Object beforeTryInsert(S storable) throws PersistException {
+        return beforeInsert(storable);
+    }
+
+    /**
+     * Called right after a storable has been successfully inserted. The
+     * default implementation does nothing.
+     *
+     * <p>Any exception thrown by this method will cause the insert operation
+     * to rollback and all remaining triggers to not run. The exception is
+     * ultimately passed to the caller of the insert method.
+     *
+     * @param storable storable after being inserted
+     * @param state object returned by beforeInsert method
+     */
+    public void afterInsert(S storable, Object state) throws PersistException {
+    }
+
+    /**
+     * Called right after a storable has been successfully inserted via
+     * tryInsert. The default implementation simply calls {@link #afterInsert}.
+     * Only override if trigger needs to distinguish between different insert
+     * variants.
+     *
+     * <p>Any exception thrown by this method will cause the tryInsert
+     * operation to rollback and all remaining triggers to not run. The
+     * exception is ultimately passed to the caller of the tryInsert method.
+     *
+     * @param storable storable after being inserted
+     * @param state object returned by beforeTryInsert method
+     * @see #abortTry
+     */
+    public void afterTryInsert(S storable, Object state) throws PersistException {
+        afterInsert(storable, state);
+    }
+
+    /**
+     * Called when an insert operation failed due to a unique constraint
+     * violation or an exception was thrown. The main purpose of this method is
+     * to allow any necessary clean-up to occur on the optional state object.
+     *
+     * <p>Any exception thrown by this method will be passed to the current
+     * thread's uncaught exception handler.
+     *
+     * @param storable storable which failed to be inserted
+     * @param state object returned by beforeInsert method, but it may be null
+     */
+    public void failedInsert(S storable, Object state) {
+    }
+
+    /**
+     * Called before a storable is to be updated. The default implementation
+     * does nothing.
+     *
+     * <p>Any exception thrown by this method will cause the update operation
+     * to rollback and all remaining triggers to not run. The exception is
+     * ultimately passed to the caller of the update method.
+     *
+     * @param storable storable before being updated
+     * @return arbitrary state object, passed to afterUpdate or failedUpdate method
+     */
+    public Object beforeUpdate(S storable) throws PersistException {
+        return null;
+    }
+
+    /**
+     * Called before a storable is to be updated via tryUpdate. The default
+     * implementation simply calls {@link #beforeUpdate}. Only override if
+     * trigger needs to distinguish between different update variants.
+     *
+     * <p>Any exception thrown by this method will cause the tryUpdate operation
+     * to rollback and all remaining triggers to not run. The exception is
+     * ultimately passed to the caller of the tryUpdate method.
+     *
+     * @param storable storable before being updated
+     * @return arbitrary state object, passed to afterTryUpdate or failedUpdate method
+     * @see #abortTry
+     */
+    public Object beforeTryUpdate(S storable) throws PersistException {
+        return beforeUpdate(storable);
+    }
+
+    /**
+     * Called right after a storable has been successfully updated. The default
+     * implementation does nothing.
+     *
+     * <p>Any exception thrown by this method will cause the update operation
+     * to rollback and all remaining triggers to not run. The exception is
+     * ultimately passed to the caller of the update method.
+     *
+     * @param storable storable after being updated
+     * @param state optional object returned by beforeUpdate method
+     */
+    public void afterUpdate(S storable, Object state) throws PersistException {
+    }
+
+    /**
+     * Called right after a storable has been successfully updated via
+     * tryUpdate. The default implementation simply calls {@link #afterUpdate}.
+     * Only override if trigger needs to distinguish between different update
+     * variants.
+     *
+     * <p>Any exception thrown by this method will cause the tryUpdate
+     * operation to rollback and all remaining triggers to not run. The
+     * exception is ultimately passed to the caller of the tryUpdate method.
+     *
+     * @param storable storable after being updated
+     * @param state object returned by beforeTryUpdate method
+     * @see #abortTry
+     */
+    public void afterTryUpdate(S storable, Object state) throws PersistException {
+        afterUpdate(storable, state);
+    }
+
+    /**
+     * Called when an update operation failed because the record was missing or
+     * an exception was thrown. The main purpose of this method is to allow any
+     * necessary clean-up to occur on the optional state object.
+     *
+     * <p>Any exception thrown by this method will be passed to the current
+     * thread's uncaught exception handler.
+     *
+     * @param storable storable which failed to be updated
+     * @param state optional object returned by beforeUpdate
+     * method, but it may be null
+     */
+    public void failedUpdate(S storable, Object state) {
+    }
+
+    /**
+     * Called before a storable is to be deleted. The default implementation
+     * does nothing.
+     *
+     * <p>Any exception thrown by this method will cause the delete operation
+     * to rollback and all remaining triggers to not run. The exception is
+     * ultimately passed to the caller of the delete method.
+     *
+     * @param storable storable before being deleted
+     * @return arbitrary state object, passed to afterDelete or failedDelete method
+     */
+    public Object beforeDelete(S storable) throws PersistException {
+        return null;
+    }
+
+    /**
+     * Called before a storable is to be deleted via tryDelete. The default
+     * implementation simply calls {@link #beforeDelete}. Only override if
+     * trigger needs to distinguish between different delete variants.
+     *
+     * <p>Any exception thrown by this method will cause the tryDelete operation
+     * to rollback and all remaining triggers to not run. The exception is
+     * ultimately passed to the caller of the tryDelete method.
+     *
+     * @param storable storable before being deleted
+     * @return arbitrary state object, passed to afterTryDelete or failedDelete method
+     * @see #abortTry
+     */
+    public Object beforeTryDelete(S storable) throws PersistException {
+        return beforeDelete(storable);
+    }
+
+    /**
+     * Called right after a storable has been successfully deleted. The default
+     * implementation does nothing.
+     *
+     * <p>Any exception thrown by this method will cause the delete operation
+     * to rollback and all remaining triggers to not run. The exception is
+     * ultimately passed to the caller of the delete method.
+     *
+     * @param storable storable after being deleted
+     * @param state optional object returned by beforeDelete method
+     */
+    public void afterDelete(S storable, Object state) throws PersistException {
+    }
+
+    /**
+     * Called right after a storable has been successfully deleted via
+     * tryDelete. The default implementation simply calls {@link #afterDelete}.
+     * Only override if trigger needs to distinguish between different delete
+     * variants.
+     *
+     * <p>Any exception thrown by this method will cause the tryDelete
+     * operation to rollback and all remaining triggers to not run. The
+     * exception is ultimately passed to the caller of the tryDelete method.
+     *
+     * @param storable storable after being deleted
+     * @param state object returned by beforeTryDelete method
+     * @see #abortTry
+     */
+    public void afterTryDelete(S storable, Object state) throws PersistException {
+        afterDelete(storable, state);
+    }
+
+    /**
+     * Called when an delete operation failed because the record was missing or
+     * an exception was thrown. The main purpose of this method is to allow any
+     * necessary clean-up to occur on the optional state object.
+     *
+     * <p>Any exception thrown by this method will be passed to the current
+     * thread's uncaught exception handler.
+     *
+     * @param storable storable which failed to be deleted
+     * @param state optional object returned by beforeDelete
+     * method, but it may be null
+     */
+    public void failedDelete(S storable, Object state) {
+    }
+
+    /**
+     * Call to quickly abort a "try" operation, returning false to the
+     * caller. This method should not be called by a non-try trigger method,
+     * since the caller gets thrown an exception with an incomplete stack trace.
+     *
+     * <p>This method never returns normally, but as a convenience, a return
+     * type is defined. The abort exception can be thrown by {@code throw abortTry()},
+     * but the {@code throw} keyword is not needed.
+     */
+    protected Abort abortTry() throws Abort {
+        // Throwing and catching an exception is not terribly expensive, but
+        // creating a new exception is more than an order of magnitude slower.
+        // Therefore, re-use the same instance. It has no stack trace since it
+        // would be meaningless.
+        throw Abort.INSTANCE;
+    }
+
+    public static final class Abort extends PersistException {
+        static final Abort INSTANCE = new Abort();
+
+        private Abort() {
+            super("Trigger aborted operation", null);
+        }
+
+        private Abort(String message) {
+            super(message);
+            super.fillInStackTrace();
+        }
+
+        /**
+         * Override to remove the stack trace.
+         */
+        @Override
+        public Throwable fillInStackTrace() {
+            return null;
+        }
+
+        /**
+         * Returns this exception but with a fresh stack trace. The trace does
+         * not include the original thrower of this exception.
+         */
+        public Abort withStackTrace() {
+            Abort a = new Abort(getMessage());
+
+            StackTraceElement[] trace = a.getStackTrace();
+            if (trace != null && trace.length > 1) {
+                // Trim off this method from the trace, which is element 0.
+                StackTraceElement[] trimmed = new StackTraceElement[trace.length - 1];
+                System.arraycopy(trace, 1, trimmed, 0, trimmed.length);
+                a.setStackTrace(trimmed);
+            }
+
+            return a;
+        }
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/UniqueConstraintException.java b/src/main/java/com/amazon/carbonado/UniqueConstraintException.java
new file mode 100644
index 0000000..a3c89b3
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/UniqueConstraintException.java
@@ -0,0 +1,47 @@
+/*
+ * 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;
+
+/**
+ * A UniqueConstraintException is thrown if the {@link Repository} storage
+ * layer has a unique constraint check defined for a property, and a violation
+ * has been detected.
+ *
+ * @author Brian S O'Neill
+ */
+public class UniqueConstraintException extends ConstraintException {
+
+    private static final long serialVersionUID = 8887715791827869110L;
+
+    public UniqueConstraintException() {
+        super();
+    }
+
+    public UniqueConstraintException(String message) {
+        super(message);
+    }
+
+    public UniqueConstraintException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public UniqueConstraintException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/UnsupportedTypeException.java b/src/main/java/com/amazon/carbonado/UnsupportedTypeException.java
new file mode 100644
index 0000000..46535a4
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/UnsupportedTypeException.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+/**
+ * Thrown by a dependent {@link Repository} which cannot support an {@link
+ * Independent} {@link Storable}.
+ *
+ * @author Brian S O'Neill
+ */
+public class UnsupportedTypeException extends SupportException {
+    private static final long serialVersionUID = 1L;
+
+    private final Class<? extends Storable> mType;
+
+    public UnsupportedTypeException(Class<? extends Storable> type) {
+        super("Independent type not supported: " + type.getName());
+        mType = type;
+    }
+
+    public Class<? extends Storable> getType() {
+        return mType;
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/Version.java b/src/main/java/com/amazon/carbonado/Version.java
new file mode 100644
index 0000000..9bb326a
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/Version.java
@@ -0,0 +1,78 @@
+/*
+ * 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.*;
+
+/**
+ * Designates a {@link Storable} property as being the authoritative version
+ * number for the entire Storable instance. Only one property can have this
+ * designation.
+ *
+ * <p>Support for the version property falls into three categories.  A
+ * repository may manage the version; it may respect the version; or it may
+ * merely check the version.
+ *
+ * <p><b>Manage</b>: Each storable with a version property must have one and
+ * only one repository which is responsible for managing the version property.
+ * That repository takes responsibility for establishing the version on insert,
+ * and for auto-incrementing it on update.  Under no circumstances should the
+ * version property be incremented manually; this can result in a false
+ * optimistic lock exception, or worse may allow the persistent record to
+ * become corrupted.  Prior to incrementing, these repositories will verify
+ * that the version exactly matches the version of the current record, throwing
+ * an {@link OptimisticLockException} otherwise.  The JDBC repository is the
+ * canonical example of this sort of repository.
+ *
+ * <p><b>Respect</b>: Repositories which respect the version use the version to
+ * guarantee that updates are idempotent -- that is, that an update is applied
+ * once and only once.  These repositories will check that the version property
+ * is strictly greater than the version of the current record, and will
+ * (silently) ignore changes which fail this check.
+ *
+ * <p><b>Check</b>: Philosophically, a version property can be considered part
+ * of the identity of the storable.  That is, if the storable has a version
+ * property, it cannot be considered fully specified unless that property is
+ * specified.  Thus, the minimal required support for all repositories is to
+ * check that the version is specified on update.  All repositories -- even
+ * those which neither check nor manage the version -- will throw an {@link
+ * IllegalStateException} if the version property is not set before update.
+ *
+ * <p>The actual type of the version property can be anything, but some
+ * repositories might only support integers. For maximum portability, version
+ * properties should be a regular 32-bit int.
+ *
+ * <p>Example:<pre>
+ * public interface UserInfo extends Storable {
+ *     <b>&#64;Version</b>
+ *     int getRecordVersionNumber();
+ *
+ *     ...
+ * }
+ * </pre>
+ *
+ * @author Brian S O'Neill
+ * @author Don Schneider
+ * @see OptimisticLockException
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD})
+public @interface Version {
+}
diff --git a/src/main/java/com/amazon/carbonado/package-info.java b/src/main/java/com/amazon/carbonado/package-info.java
new file mode 100644
index 0000000..62753ed
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+/**
+ * Main user-level interfaces, classes, and annotations for Carbonado.
+ */
+package com.amazon.carbonado;
-- 
cgit v1.2.3