From 04b06d8fa2ced539126f74f4d8a19c29b62c5730 Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" Date: Wed, 30 Aug 2006 02:05:21 +0000 Subject: Add layout evolution support --- .../java/com/amazon/carbonado/layout/Layout.java | 371 +++++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100644 src/main/java/com/amazon/carbonado/layout/Layout.java (limited to 'src/main/java/com/amazon/carbonado/layout/Layout.java') 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> cReconstructed; + + static { + cReconstructed = Collections.synchronizedMap(new SoftValuedHashMap()); + } + + static Class reconstruct(final Layout layout, ClassLoader loader) + throws FetchException, SupportException + { + synchronized (cReconstructed) { + Long key = layout.getLayoutID(); + Class 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 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> properties = info.getAllProperties().values(); + List list = new ArrayList(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 getDataProperties() throws FetchException { + List all = getAllProperties(); + List data = new ArrayList(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 getAllProperties() throws FetchException { + if (mAllProperties == null) { + Cursor cursor = mStoredLayout.getProperties() + .orderBy("ordinal") + .fetch(); + + List list = new ArrayList(); + + 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 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 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 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 reconstruct(ClassLoader loader) + throws FetchException, SupportException + { + Class 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(); + } + } +} -- cgit v1.2.3