summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBrian S. O'Neill <bronee@gmail.com>2006-08-30 02:05:21 +0000
committerBrian S. O'Neill <bronee@gmail.com>2006-08-30 02:05:21 +0000
commit04b06d8fa2ced539126f74f4d8a19c29b62c5730 (patch)
tree058d93bd2757f587f141ac424b064740e447dcf5 /src
parent1205ea08cc9e53de40b71447a79daf378cb0a205 (diff)
Add layout evolution support
Diffstat (limited to 'src')
-rw-r--r--src/main/java/com/amazon/carbonado/layout/Layout.java371
-rw-r--r--src/main/java/com/amazon/carbonado/layout/LayoutCapability.java51
-rw-r--r--src/main/java/com/amazon/carbonado/layout/LayoutFactory.java243
-rw-r--r--src/main/java/com/amazon/carbonado/layout/LayoutProperty.java191
-rw-r--r--src/main/java/com/amazon/carbonado/layout/StoredLayout.java107
-rw-r--r--src/main/java/com/amazon/carbonado/layout/StoredLayoutProperty.java126
-rw-r--r--src/main/java/com/amazon/carbonado/layout/Unevolvable.java27
-rw-r--r--src/main/java/com/amazon/carbonado/layout/package-info.java40
8 files changed, 1156 insertions, 0 deletions
diff --git a/src/main/java/com/amazon/carbonado/layout/Layout.java b/src/main/java/com/amazon/carbonado/layout/Layout.java
new file mode 100644
index 0000000..b751fb1
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/layout/Layout.java
@@ -0,0 +1,371 @@
+/*
+ * 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.layout;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.joda.time.DateTime;
+
+import org.cojen.util.SoftValuedHashMap;
+
+import com.amazon.carbonado.Cursor;
+import com.amazon.carbonado.FetchException;
+import com.amazon.carbonado.FetchNoneException;
+import com.amazon.carbonado.PersistException;
+import com.amazon.carbonado.Query;
+import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.SupportException;
+import com.amazon.carbonado.info.StorableInfo;
+import com.amazon.carbonado.info.StorableProperty;
+import com.amazon.carbonado.synthetic.SyntheticKey;
+import com.amazon.carbonado.synthetic.SyntheticProperty;
+import com.amazon.carbonado.synthetic.SyntheticStorableBuilder;
+import com.amazon.carbonado.util.AnnotationDescPrinter;
+
+/**
+ * Describes the layout of a specific generation of a storable.
+ *
+ * @author Brian S O'Neill
+ * @see LayoutFactory
+ */
+public class Layout {
+ private static Map<Long, Class<? extends Storable>> cReconstructed;
+
+ static {
+ cReconstructed = Collections.synchronizedMap(new SoftValuedHashMap());
+ }
+
+ static Class<? extends Storable> reconstruct(final Layout layout, ClassLoader loader)
+ throws FetchException, SupportException
+ {
+ synchronized (cReconstructed) {
+ Long key = layout.getLayoutID();
+ Class<? extends Storable> clazz = cReconstructed.get(key);
+ if (clazz != null) {
+ return clazz;
+ }
+
+ SyntheticStorableBuilder builder =
+ new SyntheticStorableBuilder(layout.getStorableTypeName(), loader);
+
+ // Make sure reconstructed class encodes the same as before.
+ builder.setEvolvable(true);
+
+ builder.setClassNameProvider(new SyntheticStorableBuilder.ClassNameProvider() {
+ public String getName() {
+ return layout.getStorableTypeName();
+ }
+
+ // The name of the auto-generated class should not be made with
+ // createExplicit. Otherwise, property type changes will
+ // conflict, since the reconstructed class name is the same.
+ public boolean isExplicit() {
+ return false;
+ }
+ });
+
+ SyntheticKey primaryKey = builder.addPrimaryKey();
+
+ for (LayoutProperty property : layout.getAllProperties()) {
+ Class propClass = property.getPropertyType(loader);
+ if (Query.class.isAssignableFrom(propClass) ||
+ Storable.class.isAssignableFrom(propClass)) {
+ // Accidentally stored join property in layout, caused by a
+ // bug in a previous version of Layout. Move on.
+ continue;
+ }
+
+ SyntheticProperty synthProp =
+ builder.addProperty(property.getPropertyName(), propClass);
+
+ synthProp.setIsNullable(property.isNullable());
+ synthProp.setIsVersion(property.isVersion());
+
+ if (property.isPrimaryKeyMember()) {
+ primaryKey.addProperty(property.getPropertyName());
+ }
+
+ if (property.getAdapterTypeName() != null) {
+ String desc = property.getAdapterParams();
+ if (desc == null) {
+ desc = AnnotationDescPrinter
+ .makePlainDescriptor(property.getAdapterTypeName());
+ }
+ synthProp.addAccessorAnnotationDescriptor(desc);
+ }
+ }
+
+ clazz = builder.build();
+
+ cReconstructed.put(key, clazz);
+ return clazz;
+ }
+ }
+
+ private final LayoutFactory mLayoutFactory;
+ private final StoredLayout mStoredLayout;
+
+ private transient List<LayoutProperty> mAllProperties;
+
+ /**
+ * Creates a Layout around an existing storable.
+ */
+ Layout(LayoutFactory factory, StoredLayout storedLayout) {
+ mLayoutFactory = factory;
+ mStoredLayout = storedLayout;
+ }
+
+ /**
+ * Copies layout information into freshly prepared storables. Call insert
+ * (on this class) to persist them.
+ */
+ Layout(LayoutFactory factory, StorableInfo<?> info, long layoutID) {
+ mLayoutFactory = factory;
+
+ StoredLayout storedLayout = factory.mLayoutStorage.prepare();
+ mStoredLayout = storedLayout;
+
+ storedLayout.setLayoutID(layoutID);
+ storedLayout.setStorableTypeName(info.getStorableType().getName());
+ storedLayout.setCreationTimestamp(System.currentTimeMillis());
+ try {
+ storedLayout.setCreationUser(System.getProperty("user.name"));
+ } catch (SecurityException e) {
+ // Can't get user, no big deal.
+ }
+ try {
+ storedLayout.setCreationHost(InetAddress.getLocalHost().getHostName());
+ } catch (UnknownHostException e) {
+ // Can't get host, no big deal.
+ } catch (SecurityException e) {
+ // Can't get host, no big deal.
+ }
+
+ Collection<? extends StorableProperty<?>> properties = info.getAllProperties().values();
+ List<LayoutProperty> list = new ArrayList<LayoutProperty>(properties.size());
+ int ordinal = 0;
+ for (StorableProperty<?> property : properties) {
+ if (property.isJoin()) {
+ continue;
+ }
+ StoredLayoutProperty storedLayoutProperty = mLayoutFactory.mPropertyStorage.prepare();
+ list.add(new LayoutProperty(storedLayoutProperty, property, layoutID, ordinal));
+ ordinal++;
+ }
+
+ mAllProperties = Collections.unmodifiableList(list);
+ }
+
+ /**
+ * Returns a unique identifier for this layout.
+ */
+ public long getLayoutID() {
+ return mStoredLayout.getLayoutID();
+ }
+
+ /**
+ * Storable type name is a fully qualified Java class name.
+ */
+ public String getStorableTypeName() {
+ return mStoredLayout.getStorableTypeName();
+ }
+
+ /**
+ * Returns the generation of this layout, where zero represents the first
+ * generation.
+ */
+ public int getGeneration() {
+ return mStoredLayout.getGeneration();
+ }
+
+ /**
+ * Returns all the non-primary key properties of this layout, in their
+ * proper order.
+ */
+ public List<LayoutProperty> getDataProperties() throws FetchException {
+ List<LayoutProperty> all = getAllProperties();
+ List<LayoutProperty> data = new ArrayList<LayoutProperty>(all.size() - 1);
+ for (LayoutProperty property : all) {
+ if (!property.isPrimaryKeyMember()) {
+ data.add(property);
+ }
+ }
+ return Collections.unmodifiableList(data);
+ }
+
+ /**
+ * Returns all the properties of this layout, in their proper order.
+ */
+ public List<LayoutProperty> getAllProperties() throws FetchException {
+ if (mAllProperties == null) {
+ Cursor <StoredLayoutProperty> cursor = mStoredLayout.getProperties()
+ .orderBy("ordinal")
+ .fetch();
+
+ List<LayoutProperty> list = new ArrayList<LayoutProperty>();
+
+ while (cursor.hasNext()) {
+ list.add(new LayoutProperty(cursor.next()));
+ }
+
+ mAllProperties = Collections.unmodifiableList(list);
+ }
+
+ return mAllProperties;
+ }
+
+ /**
+ * Returns the date and time for when this layout generation was created.
+ */
+ public DateTime getCreationDateTime() {
+ return new DateTime(mStoredLayout.getCreationTimestamp());
+ }
+
+ /**
+ * Returns the user that created this layout generation.
+ */
+ public String getCreationUser() {
+ return mStoredLayout.getCreationUser();
+ }
+
+ /**
+ * Returns the host machine that created this generation.
+ */
+ public String getCreationHost() {
+ return mStoredLayout.getCreationHost();
+ }
+
+ /**
+ * Returns the layout for a particular generation of this layout's type.
+ *
+ * @throws FetchNoneException if generation not found
+ */
+ public Layout getGeneration(int generation) throws FetchNoneException, FetchException {
+ StoredLayout storedLayout = mLayoutFactory.mLayoutStorage
+ .query("storableTypeName = ? & generation = ?")
+ .with(getStorableTypeName()).with(generation)
+ .loadOne();
+ return new Layout(mLayoutFactory, storedLayout);
+ }
+
+ /**
+ * Returns the previous known generation of the storable's layout, or null
+ * if none.
+ *
+ * @return a layout with a lower generation, or null if none
+ */
+ public Layout previousGeneration() throws FetchException {
+ Cursor<StoredLayout> cursor = mLayoutFactory.mLayoutStorage
+ .query("storableTypeName = ? & generation < ?")
+ .with(getStorableTypeName()).with(getGeneration())
+ .orderBy("-generation")
+ .fetch();
+
+ try {
+ if (cursor.hasNext()) {
+ return new Layout(mLayoutFactory, cursor.next());
+ }
+ } finally {
+ cursor.close();
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the next known generation of the storable's layout, or null
+ * if none.
+ *
+ * @return a layout with a higher generation, or null if none
+ */
+ public Layout nextGeneration() throws FetchException {
+ Cursor<StoredLayout> cursor =
+ mLayoutFactory.mLayoutStorage.query("storableTypeName = ? & generation > ?")
+ .with(getStorableTypeName()).with(getGeneration())
+ .orderBy("+generation")
+ .fetch();
+
+ try {
+ if (cursor.hasNext()) {
+ return new Layout(mLayoutFactory, cursor.next());
+ }
+ } finally {
+ cursor.close();
+ }
+
+ return null;
+ }
+
+ /**
+ * Reconstructs the storable type defined by this layout by returning an
+ * auto-generated class. The reconstructed storable type will not contain
+ * everything in the original, but rather the minimum required to decode
+ * persisted instances.
+ */
+ public Class<? extends Storable> reconstruct() throws FetchException, SupportException {
+ return reconstruct(null);
+ }
+
+ /**
+ * Reconstructs the storable type defined by this layout by returning an
+ * auto-generated class. The reconstructed storable type will not contain
+ * everything in the original, but rather the minimum required to decode
+ * persisted instances.
+ *
+ * @param loader optional ClassLoader to load reconstruct class into, if it
+ * has not been loaded yet
+ */
+ public Class<? extends Storable> reconstruct(ClassLoader loader)
+ throws FetchException, SupportException
+ {
+ Class<? extends Storable> reconstructed = reconstruct(this, loader);
+ mLayoutFactory.registerReconstructed(reconstructed, this);
+ return reconstructed;
+ }
+
+ /**
+ * Returns true if the given layout matches this one. Layout ID,
+ * generation, and creation info is not considered in the comparison.
+ */
+ public boolean equalLayouts(Layout layout) throws FetchException {
+ if (this == layout) {
+ return true;
+ }
+ return getStorableTypeName().equals(layout.getStorableTypeName())
+ && getAllProperties().equals(layout.getAllProperties());
+ }
+
+ // Assumes caller is in a transaction.
+ void insert(int generation) throws PersistException {
+ if (mAllProperties == null) {
+ throw new IllegalStateException();
+ }
+ mStoredLayout.setGeneration(generation);
+ mStoredLayout.insert();
+ for (LayoutProperty property : mAllProperties) {
+ property.insert();
+ }
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/layout/LayoutCapability.java b/src/main/java/com/amazon/carbonado/layout/LayoutCapability.java
new file mode 100644
index 0000000..e43fc23
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/layout/LayoutCapability.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.layout;
+
+import com.amazon.carbonado.FetchException;
+import com.amazon.carbonado.FetchNoneException;
+import com.amazon.carbonado.PersistException;
+import com.amazon.carbonado.Storable;
+
+import com.amazon.carbonado.capability.Capability;
+
+/**
+ * Capability to get layout information on any storable generation.
+ *
+ * @author Brian S O'Neill
+ */
+public interface LayoutCapability extends Capability {
+ /**
+ * Returns the layout matching the current definition of the given type.
+ *
+ * @throws PersistException if type represents a new generation, but
+ * persisting this information failed
+ */
+ public Layout layoutFor(Class<? extends Storable> type)
+ throws FetchException, PersistException;
+
+ /**
+ * Returns the layout for a particular generation of the given type.
+ *
+ * @param generation desired generation
+ * @throws FetchNoneException if generation not found
+ */
+ public Layout layoutFor(Class<? extends Storable> type, int generation)
+ throws FetchException, FetchNoneException;
+}
diff --git a/src/main/java/com/amazon/carbonado/layout/LayoutFactory.java b/src/main/java/com/amazon/carbonado/layout/LayoutFactory.java
new file mode 100644
index 0000000..3557db0
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/layout/LayoutFactory.java
@@ -0,0 +1,243 @@
+/*
+ * 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.layout;
+
+import java.lang.annotation.Annotation;
+import java.util.Map;
+
+import org.cojen.util.SoftValuedHashMap;
+
+import com.amazon.carbonado.Cursor;
+import com.amazon.carbonado.FetchException;
+import com.amazon.carbonado.FetchNoneException;
+import com.amazon.carbonado.PersistException;
+import com.amazon.carbonado.Repository;
+import com.amazon.carbonado.RepositoryException;
+import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.Storage;
+import com.amazon.carbonado.Transaction;
+
+import com.amazon.carbonado.info.StorableInfo;
+import com.amazon.carbonado.info.StorableIntrospector;
+import com.amazon.carbonado.info.StorableProperty;
+import com.amazon.carbonado.info.StorablePropertyAdapter;
+import com.amazon.carbonado.info.StorablePropertyAnnotation;
+
+/**
+ * Factory for obtaining references to storable layouts.
+ *
+ * @author Brian S O'Neill
+ */
+public class LayoutFactory implements LayoutCapability {
+ // The first entry is the primary hash multiplier. Subsequent ones are
+ // rehash multipliers.
+ private static final int[] HASH_MULTIPLIERS = {31, 63};
+
+ final Repository mRepository;
+ final Storage<StoredLayout> mLayoutStorage;
+ final Storage<StoredLayoutProperty> mPropertyStorage;
+
+ private Map<Class<? extends Storable>, Layout> mReconstructed;
+
+ /**
+ * @throws com.amazon.carbonado.SupportException if underlying repository
+ * does not support the storables for persisting storable layouts
+ */
+ public LayoutFactory(Repository repo) throws RepositoryException {
+ mRepository = repo;
+ mLayoutStorage = repo.storageFor(StoredLayout.class);
+ mPropertyStorage = repo.storageFor(StoredLayoutProperty.class);
+ }
+
+ /**
+ * Returns the layout matching the current definition of the given type.
+ *
+ * @throws PersistException if type represents a new generation, but
+ * persisting this information failed
+ */
+ public Layout layoutFor(Class<? extends Storable> type)
+ throws FetchException, PersistException
+ {
+ synchronized (this) {
+ if (mReconstructed != null) {
+ Layout layout = mReconstructed.get(type);
+ if (layout != null) {
+ return layout;
+ }
+ }
+ }
+
+ StorableInfo<?> info = StorableIntrospector.examine(type);
+
+ Transaction txn = mRepository.enterTransaction();
+ try {
+ // If type represents a new generation, then a new layout needs to
+ // be inserted.
+ Layout newLayout = null;
+
+ for (int i=0; i<HASH_MULTIPLIERS.length; i++) {
+ // Generate an identifier which has a high likelyhood of being unique.
+ long layoutID = mixInHash(0L, info, HASH_MULTIPLIERS[i]);
+
+ // Initially use for comparison purposes.
+ newLayout = new Layout(this, info, layoutID);
+
+ StoredLayout storedLayout = mLayoutStorage.prepare();
+ storedLayout.setLayoutID(layoutID);
+
+ if (!storedLayout.tryLoad()) {
+ // Not found, so break out and insert.
+ break;
+ }
+
+ Layout knownLayout = new Layout(this, storedLayout);
+ if (knownLayout.equalLayouts(newLayout)) {
+ // Type does not represent a new generation. Return
+ // existing layout.
+ return knownLayout;
+ }
+
+ // If this point is reached, then there was a hash collision in
+ // the generated layout ID. This should be extremely rare.
+ // Rehash and try again.
+
+ if (i >= HASH_MULTIPLIERS.length - 1) {
+ // No more rehashes to attempt. This should be extremely,
+ // extremely rare, unless there is a bug somewhere.
+ throw new FetchException("Unable to generate unique layout identifier");
+ }
+ }
+
+ // If this point is reached, then type represents a new
+ // generation. Calculate next generation value and insert.
+
+ assert(newLayout != null);
+ int generation = 0;
+
+ Cursor<StoredLayout> cursor = mLayoutStorage
+ .query("storableTypeName = ?")
+ .with(info.getStorableType().getName())
+ .orderBy("-generation")
+ .fetch();
+
+ try {
+ if (cursor.hasNext()) {
+ generation = cursor.next().getGeneration() + 1;
+ }
+ } finally {
+ cursor.close();
+ }
+
+ newLayout.insert(generation);
+ txn.commit();
+
+ return newLayout;
+ } finally {
+ txn.exit();
+ }
+ }
+
+ /**
+ * Returns the layout for a particular generation of the given type.
+ *
+ * @param generation desired generation
+ * @throws FetchNoneException if generation not found
+ */
+ public Layout layoutFor(Class<? extends Storable> type, int generation)
+ throws FetchException, FetchNoneException
+ {
+ StoredLayout storedLayout =
+ mLayoutStorage.query("storableTypeName = ? & generation = ?")
+ .with(type.getName()).with(generation)
+ .loadOne();
+ return new Layout(this, storedLayout);
+ }
+
+ synchronized void registerReconstructed
+ (Class<? extends Storable> reconstructed, Layout layout)
+ {
+ if (mReconstructed == null) {
+ mReconstructed = new SoftValuedHashMap();
+ }
+ mReconstructed.put(reconstructed, layout);
+ }
+
+ /**
+ * Creates a long hash code that attempts to mix in all relevent layout
+ * elements.
+ */
+ private long mixInHash(long hash, StorableInfo<?> info, int multiplier) {
+ hash = mixInHash(hash, info.getStorableType().getName(), multiplier);
+
+ for (StorableProperty<?> property : info.getAllProperties().values()) {
+ if (!property.isJoin()) {
+ hash = mixInHash(hash, property, multiplier);
+ }
+ }
+
+ return hash;
+ }
+
+ /**
+ * Creates a long hash code that attempts to mix in all relevent layout
+ * elements.
+ */
+ private long mixInHash(long hash, StorableProperty<?> property, int multiplier) {
+ hash = mixInHash(hash, property.getName(), multiplier);
+ hash = mixInHash(hash, property.getType().getName(), multiplier);
+ hash = hash * multiplier + (property.isNullable() ? 1 : 2);
+ hash = hash * multiplier + (property.isPrimaryKeyMember() ? 1 : 2);
+
+ // Keep this in for compatibility with prior versions of hash code.
+ hash = hash * multiplier + 1;
+
+ if (property.getAdapter() != null) {
+ // Keep this in for compatibility with prior versions of hash code.
+ hash += 1;
+
+ StorablePropertyAdapter adapter = property.getAdapter();
+ StorablePropertyAnnotation annotation = adapter.getAnnotation();
+
+ hash = mixInHash(hash, annotation.getAnnotationType().getName(), multiplier);
+
+ // Annotation may contain parameters which affect how property
+ // value is stored. So mix that in too.
+ Annotation ann = annotation.getAnnotation();
+ if (ann != null) {
+ // Okay to mix in annotation hash code since Annotation
+ // documentation defines the implementation. It should remain
+ // stable between releases, but it is not critical that the
+ // hash code always comes out the same. The result would be a
+ // duplicate stored layout, but with a different generation.
+ // Stored entries will be converted from the "different"
+ // generation, causing a very slight performance degradation.
+ hash = hash * multiplier + ann.hashCode();
+ }
+ }
+
+ return hash;
+ }
+
+ private long mixInHash(long hash, CharSequence value, int multiplier) {
+ for (int i=value.length(); --i>=0; ) {
+ hash = hash * multiplier + value.charAt(i);
+ }
+ return hash;
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/layout/LayoutProperty.java b/src/main/java/com/amazon/carbonado/layout/LayoutProperty.java
new file mode 100644
index 0000000..4a7dea1
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/layout/LayoutProperty.java
@@ -0,0 +1,191 @@
+/*
+ * 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.layout;
+
+import org.cojen.classfile.TypeDesc;
+
+import com.amazon.carbonado.PersistException;
+import com.amazon.carbonado.SupportException;
+
+import com.amazon.carbonado.info.StorableProperty;
+import com.amazon.carbonado.info.StorablePropertyAdapter;
+import com.amazon.carbonado.info.StorablePropertyAnnotation;
+
+import com.amazon.carbonado.util.AnnotationDescPrinter;
+
+/**
+ * Describes how a property is defined in a specific generation of a storable.
+ *
+ * @author Brian S O'Neill
+ * @see Layout
+ */
+public class LayoutProperty {
+ private final StoredLayoutProperty mStoredLayoutProperty;
+
+ /**
+ * Creates a LayoutProperty around an existing storable.
+ */
+ LayoutProperty(StoredLayoutProperty storedLayoutProperty) {
+ mStoredLayoutProperty = storedLayoutProperty;
+ }
+
+ /**
+ * Copies properties into a freshly prepared storable. Call insert (on this
+ * class) to persist it.
+ *
+ * @param storedLayoutProperty freshly prepared storable
+ * @param property source of data to copy into storable
+ */
+ LayoutProperty(StoredLayoutProperty storedLayoutProperty,
+ StorableProperty<?> property,
+ long layoutID,
+ int ordinal)
+ {
+ mStoredLayoutProperty = storedLayoutProperty;
+
+ storedLayoutProperty.setLayoutID(layoutID);
+ storedLayoutProperty.setOrdinal(ordinal);
+ storedLayoutProperty.setPropertyName(property.getName());
+ storedLayoutProperty.setPropertyTypeDescriptor
+ (TypeDesc.forClass(property.getType()).getDescriptor());
+ storedLayoutProperty.setNullable(property.isNullable());
+ storedLayoutProperty.setVersion(property.isVersion());
+ storedLayoutProperty.setPrimaryKeyMember(property.isPrimaryKeyMember());
+
+ if (property.getAdapter() != null) {
+ StorablePropertyAdapter adapter = property.getAdapter();
+ StorablePropertyAnnotation spa = adapter.getAnnotation();
+ if (spa == null || spa.getAnnotation() == null) {
+ storedLayoutProperty.setAdapterTypeName(null);
+ storedLayoutProperty.setAdapterParams(null);
+ } else {
+ storedLayoutProperty.setAdapterTypeName(spa.getAnnotationType().getName());
+
+ StringBuilder b = new StringBuilder();
+ AnnotationDescPrinter printer = new AnnotationDescPrinter(true, b);
+ printer.visit(spa.getAnnotation());
+
+ storedLayoutProperty.setAdapterParams(b.toString());
+ }
+ }
+ }
+
+ public String getPropertyName() {
+ return mStoredLayoutProperty.getPropertyName();
+ }
+
+ /**
+ * Property type descriptor is a Java type descriptor.
+ */
+ public String getPropertyTypeDescriptor() {
+ return mStoredLayoutProperty.getPropertyTypeDescriptor();
+ }
+
+ public Class getPropertyType() throws SupportException {
+ return getPropertyType(null);
+ }
+
+ public Class getPropertyType(ClassLoader loader) throws SupportException {
+ TypeDesc type = TypeDesc.forDescriptor(getPropertyTypeDescriptor());
+ Class propClass = type.toClass(loader);
+ if (propClass == null) {
+ throw new SupportException
+ ("Unable to find class \"" + type.getRootName() + "\" for property \"" +
+ getPropertyName() + '"');
+ }
+ return propClass;
+ }
+
+ /**
+ * Returns true of property can be set to null.
+ */
+ public boolean isNullable() {
+ return mStoredLayoutProperty.isNullable();
+ }
+
+ /**
+ * Returns true if property is a member of the primary key.
+ */
+ public boolean isPrimaryKeyMember() {
+ return mStoredLayoutProperty.isPrimaryKeyMember();
+ }
+
+ /**
+ * Returns true if this property is the designated version number for the
+ * Storable.
+ */
+ public boolean isVersion() {
+ return mStoredLayoutProperty.isVersion();
+ }
+
+ /**
+ * Adapter type name is a fully qualified Java class name. If property has
+ * no adapter, then null is returned.
+ */
+ public String getAdapterTypeName() {
+ return mStoredLayoutProperty.getAdapterTypeName();
+ }
+
+ /**
+ * Parameters for adapter, or null if property has no explicit adapter.
+ */
+ public String getAdapterParams() {
+ return mStoredLayoutProperty.getAdapterParams();
+ }
+
+ @Override
+ public int hashCode() {
+ return mStoredLayoutProperty.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj instanceof LayoutProperty) {
+ StoredLayoutProperty thisStoredLayoutProperty = mStoredLayoutProperty;
+ StoredLayoutProperty other = ((LayoutProperty) obj).mStoredLayoutProperty;
+
+ boolean result = thisStoredLayoutProperty.equalProperties(other);
+ if (result) {
+ return result;
+ }
+
+ // Version might be only difference, which is fine.
+ if (thisStoredLayoutProperty.getVersionNumber() != other.getVersionNumber()) {
+ thisStoredLayoutProperty = thisStoredLayoutProperty.copy();
+ thisStoredLayoutProperty.setVersionNumber(other.getVersionNumber());
+ return thisStoredLayoutProperty.equalProperties(other);
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return mStoredLayoutProperty.toString();
+ }
+
+ void insert() throws PersistException {
+ mStoredLayoutProperty.insert();
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/layout/StoredLayout.java b/src/main/java/com/amazon/carbonado/layout/StoredLayout.java
new file mode 100644
index 0000000..b7065d9
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/layout/StoredLayout.java
@@ -0,0 +1,107 @@
+/*
+ * 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.layout;
+
+import com.amazon.carbonado.AlternateKeys;
+import com.amazon.carbonado.FetchException;
+import com.amazon.carbonado.Join;
+import com.amazon.carbonado.Key;
+import com.amazon.carbonado.Nullable;
+import com.amazon.carbonado.PrimaryKey;
+import com.amazon.carbonado.Query;
+import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.Version;
+
+/**
+ * Stored information regarding the layout of a Storable type, which is used
+ * internally by {@link Layout}. This interface is public only because
+ * Carbonado requires storable type definitions to be public.
+ *
+ * @author Brian S O'Neill
+ */
+@AlternateKeys({
+ @Key({"storableTypeName", "generation"})
+})
+@PrimaryKey("layoutID")
+public interface StoredLayout extends Storable<StoredLayout>, Unevolvable {
+ long getLayoutID();
+
+ void setLayoutID(long typeID);
+
+ /**
+ * Storable type name is a fully qualified Java class name.
+ */
+ String getStorableTypeName();
+
+ void setStorableTypeName(String typeName);
+
+ /**
+ * Generation of storable, where 0 represents the first generation.
+ */
+ int getGeneration();
+
+ void setGeneration(int generation);
+
+ /**
+ * Returns the milliseconds from 1970-01-01T00:00:00Z when this record was
+ * created.
+ */
+ long getCreationTimestamp();
+
+ void setCreationTimestamp(long timestamp);
+
+ /**
+ * Returns the user that created this generation.
+ */
+ @Nullable
+ String getCreationUser();
+
+ void setCreationUser(String user);
+
+ /**
+ * Returns the host machine that created this generation.
+ */
+ @Nullable
+ String getCreationHost();
+
+ void setCreationHost(String host);
+
+ @Join
+ Query<StoredLayoutProperty> getProperties() throws FetchException;
+
+ /**
+ * Record version number for this StoredTypeLayout instance. Some encoding
+ * strategies require a version number.
+ */
+ @Version
+ int getVersionNumber();
+
+ void setVersionNumber(int version);
+
+ /**
+ * Since this record cannot evolve, this property allows it to be extended
+ * without conflicting with existing records. This record cannot evolve
+ * because an evolution strategy likely depends on this interface remaining
+ * stable, avoiding a cyclic dependency.
+ */
+ @Nullable
+ byte[] getExtraData();
+
+ void setExtraData(byte[] data);
+}
diff --git a/src/main/java/com/amazon/carbonado/layout/StoredLayoutProperty.java b/src/main/java/com/amazon/carbonado/layout/StoredLayoutProperty.java
new file mode 100644
index 0000000..96a990a
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/layout/StoredLayoutProperty.java
@@ -0,0 +1,126 @@
+/*
+ * 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.layout;
+
+import com.amazon.carbonado.AlternateKeys;
+import com.amazon.carbonado.FetchException;
+import com.amazon.carbonado.Join;
+import com.amazon.carbonado.Key;
+import com.amazon.carbonado.Nullable;
+import com.amazon.carbonado.PrimaryKey;
+import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.Version;
+
+/**
+ * Stored property joined to a {@link StoredLayout}, which is used internally
+ * by {@link LayoutProperty}. This interface is public only because Carbonado
+ * requires storable type definitions to be public.
+ *
+ * @author Brian S O'Neill
+ */
+@AlternateKeys({
+ @Key({"layoutID", "propertyName"})
+})
+@PrimaryKey({"layoutID", "ordinal"})
+public interface StoredLayoutProperty extends Storable<StoredLayoutProperty>, Unevolvable {
+ long getLayoutID();
+
+ void setLayoutID(long typeID);
+
+ /**
+ * Ordinal defines the order in which this property appears in it enclosing
+ * layout.
+ */
+ int getOrdinal();
+
+ void setOrdinal(int ordinal);
+
+ String getPropertyName();
+
+ void setPropertyName(String name);
+
+ /**
+ * Property type descriptor is a Java type descriptor.
+ */
+ String getPropertyTypeDescriptor();
+
+ void setPropertyTypeDescriptor(String type);
+
+ /**
+ * Returns true of property value can be set to null.
+ */
+ boolean isNullable();
+
+ void setNullable(boolean nullable);
+
+ /**
+ * Returns true if property is a member of the primary key.
+ */
+ boolean isPrimaryKeyMember();
+
+ void setPrimaryKeyMember(boolean pk);
+
+ /**
+ * Returns true if this property is the designated version number for the
+ * Storable.
+ */
+ boolean isVersion();
+
+ void setVersion(boolean version);
+
+ /**
+ * Adapter type name is a fully qualified Java class name. If property has
+ * no adapter, then null is returned.
+ */
+ @Nullable
+ String getAdapterTypeName();
+
+ void setAdapterTypeName(String name);
+
+ /**
+ * Parameters for adapter, or null if property has no explicit adapter.
+ */
+ @Nullable
+ String getAdapterParams();
+
+ void setAdapterParams(String params);
+
+ @Join
+ StoredLayout getEnclosingLayout() throws FetchException;
+
+ /**
+ * Record version number for this StoredPropertyLayout instance. Some
+ * encoding strategies require a version number.
+ */
+ @Version
+ int getVersionNumber();
+
+ void setVersionNumber(int version);
+
+ /**
+ * Since this record cannot evolve, this property allows it to be extended
+ * without conflicting with existing records. This record cannot evolve
+ * because an evolution strategy likely depends on this interface remaining
+ * stable, avoiding a cyclic dependency.
+ */
+ @Nullable
+ byte[] getExtraData();
+
+ void setExtraData(byte[] data);
+}
diff --git a/src/main/java/com/amazon/carbonado/layout/Unevolvable.java b/src/main/java/com/amazon/carbonado/layout/Unevolvable.java
new file mode 100644
index 0000000..5b10d5f
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/layout/Unevolvable.java
@@ -0,0 +1,27 @@
+/*
+ * 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.layout;
+
+/**
+ * Marker interface for storables that are not allowed to evolve.
+ *
+ * @author Brian S O'Neill
+ */
+public interface Unevolvable {
+}
diff --git a/src/main/java/com/amazon/carbonado/layout/package-info.java b/src/main/java/com/amazon/carbonado/layout/package-info.java
new file mode 100644
index 0000000..a3cbcf7
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/layout/package-info.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.
+ */
+
+/**
+ * Support for recording the evolution of a storable's layout, used internally
+ * by some repositories. This allows storable's to evolve. Enough information
+ * is recorded in the {@link com.amazon.carbonado.layout.Layout layout} such
+ * that an older generation can be reconstructed, allowing it to be decoded
+ * from persistent storage.
+ *
+ * <p>A storable generation is different than a storable {@link
+ * com.amazon.carbonado.Version version}. The version increases with each
+ * update of an <i>instance</i>, whereas the generation increases when the
+ * storable type definition changes. The version number is stored with each
+ * instance, and the generation is stored via the classes in this package.
+ *
+ * <p>Whenever a property is added or removed from a storable, the storable
+ * layout is assigned a new generation value. If the storable layout reverts to
+ * a previous generation's layout, no new generation value is created. Instead,
+ * the generation value of the current storable will match the previous
+ * generation.
+ *
+ * @see com.amazon.carbonado.layout.LayoutFactory
+ */
+package com.amazon.carbonado.layout;