From 2246eac504e5593cdf4a489f97be85e2cc56dcf8 Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" 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 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. + *

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

Example:

+ * @Alias("USER_INFO")
+ * @PrimaryKey("userInfoID")
+ * public interface UserInfo extends Storable<UserInfo> {
+ *     @Alias("USER_ID")
+ *     long getUserInfoID();
+ *
+ *     ...
+ * }
+ * 
+ * + * @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}. + * + *

Example:

+ * @AlternateKeys({
+ *     @Key("fullPath")
+ *     @Key({"+name", "-parentID"})
+ * })
+ * @PrimaryKey("ID")
+ * public interface FileInfo extends Storable<FileInfo> {
+ *     long getID();
+ *
+ *     String getFullPath();
+ *
+ *     String getName();
+ *
+ *     long getParentID();
+ *
+ *     ...
+ * }
+ * 
+ * + * @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. + * + *

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

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

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 { + /** + * 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: + *

+     * Cursor cursor;
+     * ...
+     * while (cursor.hasNext()) {
+     *     c.add(cursor.next());
+     * }
+     * 
+ * + *

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 c) throws FetchException; + + /** + * Copies a limited amount of remaining next elements into the given + * collection. This method is roughly equivalent to the following: + *

+     * Cursor cursor;
+     * ...
+     * while (--limit >= 0 && cursor.hasNext()) {
+     *     c.add(cursor.next());
+     * }
+     * 
+ * + * @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 c, int limit) throws FetchException; + + /** + * Copies all remaining next elements into a new modifiable list. This + * method is roughly equivalent to the following: + *
+     * Cursor<S> cursor;
+     * ...
+     * List<S> list = new ...
+     * cursor.copyInto(list);
+     * 
+ * + *

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 toList() throws FetchException; + + /** + * Copies a limited amount of remaining next elements into a new modifiable + * list. This method is roughly equivalent to the following: + *

+     * Cursor<S> cursor;
+     * ...
+     * List<S> list = new ...
+     * cursor.copyInto(list, limit);
+     * 
+ * + * @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 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. + *

+ * One example of when this might be used would be to store a calculated field in the cached + * representation of the object. It is not necessary to prevent implemented methods + * of the form "get<value>" from being inadvertently interpreted as properties of the + * storable; any implementation is by definition not a property. + *

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

Independent repositories completely ignore this annotation. + * + *

Example:

+ * public interface UserInfo extends Storable<UserInfo> {
+ *     @Independent
+ *     String getName();
+ *
+ *     ...
+ * }
+ * 
+ * + * Note: 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. + * + *

Example:

+ * @Indexes({
+ *     @Index("name"),
+ *     @Index("-lastModified"),
+ *     @Index({"length", "lastModified"})
+ * })
+ * @PrimaryKey("ID")
+ * public interface FileInfo extends Storable<FileInfo> {
+ *     long getID();
+ *
+ *     String getName();
+ *
+ *     long getLength();
+ *
+ *     long getLastModified();
+ *
+ *     ...
+ * }
+ * 
+ * + * @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: + * + *
    + *
  • {@link #READ_UNCOMMITTED} + *
  • {@link #READ_COMMITTED} + *
  • {@link #REPEATABLE_READ} + *
  • {@link #SERIALIZABLE} + *
+ * + * 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. + * + *

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

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

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

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

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

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

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

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

Example:

+ * @PrimaryKey("addressID")
+ * public interface Address extends Storable {
+ *     int getAddressID();
+ *
+ *     ...
+ * }
+ *
+ * @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.
+ *     @Join
+ *     Address getAddress() throws FetchException;
+ *
+ *     // Explicit join, equivalent to getAddress.
+ *     @Join(internal="addressID", external="addressID")
+ *     Address getCurrentAddress() throws FetchException;
+ *
+ *     @Nullable
+ *     Integer getParentID();
+ *
+ *     void setParentID(Integer value);
+ *
+ *     // Many-to-one relationship
+ *     @Nullable
+ *     @Join(internal="parentID", external="userID")
+ *     UserInfo getParent() throws FetchException;
+ *
+ *     // One-to-many relationship
+ *     @Join(internal="userID", external="parentID")
+ *     Query<UserInfo> getChildren() throws FetchException;
+ *
+ *     ...
+ * }
+ * 
+ * + * @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 mMessages; + + protected MalformedArgumentException() { + super(); + } + + protected MalformedArgumentException(String message) { + super(message); + } + + protected MalformedArgumentException(List 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 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 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 mMessages; + + public MismatchException() { + super(); + mMessages = null; + } + + public MismatchException(String message) { + super(message); + mMessages = null; + } + + public MismatchException(List 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 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. + * + *

Example:

+ * public interface UserInfo extends Storable<UserInfo> {
+ *     @Nullable
+ *     String getName();
+ *
+ *     ...
+ * }
+ * 
+ * + * @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. + * + *

Example:

+ * @PrimaryKey("userInfoID")
+ * public interface UserInfo extends Storable<UserInfo> {
+ *     long getUserInfoID();
+ *
+ *     ...
+ * }
+ * 
+ * + * @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. + * + *

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 { + /** + * Returns the specific type of Storable managed by this object. + */ + Class getStorableType(); + + /** + * Returns the query's filter. + */ + Filter getFilter(); + + /** + * Returns the query's filter values, which is null if filter has no + * parameters. + */ + FilterValues 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 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 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 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 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 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 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 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 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 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 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 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 and(Filter 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 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 or(Filter 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 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. + * + *

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

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

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

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

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 + */ + Storage storageFor(Class 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. + *

+ * To ensure exit is called, use transactions as follows: + *

+     * 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();
+     * }
+     * 
+ */ + 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 top-level 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. + * + *

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

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 getCapability(Class 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. + * + *

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

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

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

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

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

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

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

Example:

+ * @PrimaryKey("userInfoID")
+ * public interface UserInfo extends Storable<UserInfo> {
+ *     @Sequence("USER_ID_SEQ")
+ *     long getUserInfoID();
+ *
+ *     ...
+ * }
+ * 
+ * + * @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. + * + *

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Design note: the name "getStorableType" is avoided, so as not to + * conflict with a user defined property of "storableType" + */ + Class storableType(); + + /** + * Copies all supported properties, skipping any that are uninitialized. + * Specifically, calls "target.set<property>" 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<property> 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<property>" 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<property> 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<property>" 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<property> method + */ + void copyVersionProperty(S target); + + /** + * Copies all supported non-primary key properties which are unequal, + * skipping any that are uninitialized. Specifically, calls + * "target.get<property>", and if the value thus retrieved differs + * from the local value, "target.set<property>" 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<property> methods + */ + void copyUnequalProperties(S target); + + /** + * Copies all supported non-primary key properties which are + * dirty. Specifically, calls "target.set<property>" 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<property> 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}. + * + *

Storage instances are thread-safe and immutable. + * + * @author Brian S O'Neill + */ +public interface Storage { + /** + * Returns the specific type of Storable managed by this object. + */ + Class 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 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 != ?)"}. + *

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

+     * Storage<UserInfo> users;
+     * UserInfo user = users.query("ID = ?").with(123456).loadOne();
+     * 
+ * The above code will likely open a Cursor in order to verify that just + * one object was loaded. Instead, do this: + *
+     * Storage<UserInfo> users;
+     * UserInfo user = users.prepare();
+     * user.setID(123456);
+     * user.load();
+     * 
+ * The complete syntax for query filters follows. Note that: + *
    + *
  • literals are not allowed + *
  • logical 'and' operator has precedence over 'or' + *
  • logical 'not' operator has precedence over 'and' + *
  • '?' placeholders can only appear after relational operators + *
+ *
+     * Filter          = OrFilter
+     * OrFilter        = AndFilter { "|" AndFilter }
+     * AndFilter       = NotFilter { "&" NotFilter }
+     * NotFilter       = [ "!" ] EntityFilter
+     * EntityFilter    = PropertyFilter
+     *                 | "(" Filter ")"
+     * PropertyFilter  = ChainedProperty RelOp "?"
+     * RelOp           = "=" | "!=" | "<" | ">=" | ">" | "<="
+     * ChainedProperty = Identifier { "." Identifier }
+     * 
+ * + * @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 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 query(Filter 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 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 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. + * + *

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 must be invoked on every + * transaction. The following pattern is recommended: + * + *

+ * 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();
+ * }
+ * 
+ * + *

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

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

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

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

To ensure proper nesting, all "before" events are run in the + * opposite 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 { + /** + * Called before a storable is to be inserted. The default implementation + * does nothing. + * + *

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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 mType; + + public UnsupportedTypeException(Class type) { + super("Independent type not supported: " + type.getName()); + mType = type; + } + + public Class 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. + * + *

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

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

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

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

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

Example:

+ * public interface UserInfo extends Storable {
+ *     @Version
+ *     int getRecordVersionNumber();
+ *
+ *     ...
+ * }
+ * 
+ * + * @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