From 10b8b1179e4b1c90db455e68ff81c89e4cb3f015 Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" Date: Sun, 14 Mar 2010 20:13:11 +0000 Subject: Allow storable types to differ between client and server. --- .../carbonado/gen/DetachedStorableFactory.java | 18 +- .../com/amazon/carbonado/gen/StorableCopier.java | 311 +++++++++++++++++++++ .../java/com/amazon/carbonado/layout/Layout.java | 39 ++- .../com/amazon/carbonado/layout/LayoutFactory.java | 48 ++++ 4 files changed, 403 insertions(+), 13 deletions(-) create mode 100644 src/main/java/com/amazon/carbonado/gen/StorableCopier.java diff --git a/src/main/java/com/amazon/carbonado/gen/DetachedStorableFactory.java b/src/main/java/com/amazon/carbonado/gen/DetachedStorableFactory.java index 1676bae..c7ad156 100644 --- a/src/main/java/com/amazon/carbonado/gen/DetachedStorableFactory.java +++ b/src/main/java/com/amazon/carbonado/gen/DetachedStorableFactory.java @@ -37,17 +37,17 @@ import org.cojen.util.ThrowUnchecked; * @author Brian S O'Neill * @since 1.2.2 */ -public class DetachedStorableFactory { - private DetachedStorableFactory() { - } +public class DetachedStorableFactory { + private final NoSupport.Factory mFactory; - public static S create(Class type) - throws SupportException - { - return (S) QuickConstructorGenerator + public DetachedStorableFactory(Class type) throws SupportException { + mFactory = QuickConstructorGenerator .getInstance(DelegateStorableGenerator.getDelegateClass(type, null), - NoSupport.Factory.class) - .newInstance(NoSupport.THE); + NoSupport.Factory.class); + } + + public S newInstance() { + return (S) mFactory.newInstance(NoSupport.THE); } private static class NoSupport implements DelegateSupport { diff --git a/src/main/java/com/amazon/carbonado/gen/StorableCopier.java b/src/main/java/com/amazon/carbonado/gen/StorableCopier.java new file mode 100644 index 0000000..7152113 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/gen/StorableCopier.java @@ -0,0 +1,311 @@ +/* + * Copyright 2010 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.gen; + +import java.lang.reflect.Constructor; + +import java.util.WeakHashMap; + +import com.amazon.carbonado.Storable; + +import com.amazon.carbonado.info.StorableInfo; +import com.amazon.carbonado.info.StorableIntrospector; +import com.amazon.carbonado.info.StorableProperty; + +import org.cojen.classfile.ClassFile; +import org.cojen.classfile.CodeBuilder; +import org.cojen.classfile.Label; +import org.cojen.classfile.MethodInfo; +import org.cojen.classfile.Modifiers; +import org.cojen.classfile.TypeDesc; + +import org.cojen.util.ClassInjector; + +/** + * Copies properties between otherwise incompatible Storables. Only matched + * properties are copied, and primitive types are converted. + * + * @author Brian S O'Neill + * @since 1.2.2 + */ +public abstract class StorableCopier { + private static final WeakHashMap cClassKeyCache; + private static final WeakHashMap cFromCache; + + static { + cClassKeyCache = new WeakHashMap(); + cFromCache = new WeakHashMap(); + } + + static synchronized Object classKey(Class clazz) { + Object key = cClassKeyCache.get(clazz); + if (key == null) { + key = new Object(); + cClassKeyCache.put(clazz, key); + } + return key; + } + + public static synchronized From from(Class source) { + Object key = classKey(source); + From from = (From) cFromCache.get(key); + if (from == null) { + from = new From(source); + cFromCache.put(key, from); + } + return from; + } + + public static class From { + private final Class mSource; + private final WeakHashMap mCopierCache; + + From(Class source) { + mSource = source; + mCopierCache = new WeakHashMap(); + } + + public synchronized StorableCopier to(Class target) { + Object key = classKey(target); + StorableCopier copier = (StorableCopier) mCopierCache.get(key); + if (copier == null) { + if (mSource == target) { + copier = (StorableCopier) Direct.THE; + } else { + copier = new Wrapped(new Wrapper(mSource, target).generate()); + } + mCopierCache.put(key, copier); + } + return copier; + } + } + + protected StorableCopier() { + } + + public abstract void copyAllProperties(S source, T target); + + public abstract void copyPrimaryKeyProperties(S source, T target); + + public abstract void copyVersionProperty(S source, T target); + + public abstract void copyUnequalProperties(S source, T target); + + public abstract void copyDirtyProperties(S source, T target); + + private static class Wrapped + extends StorableCopier + { + private final Constructor mWrapperCtor; + + private Wrapped(Constructor ctor) { + mWrapperCtor = ctor; + } + + public void copyAllProperties(S source, T target) { + source.copyAllProperties(wrap(target)); + } + + public void copyPrimaryKeyProperties(S source, T target) { + source.copyPrimaryKeyProperties(wrap(target)); + } + + public void copyVersionProperty(S source, T target) { + source.copyVersionProperty(wrap(target)); + } + + public void copyUnequalProperties(S source, T target) { + source.copyUnequalProperties(wrap(target)); + } + + public void copyDirtyProperties(S source, T target) { + source.copyDirtyProperties(wrap(target)); + } + + private S wrap(T target) { + try { + return mWrapperCtor.newInstance(target); + } catch (Exception e) { + throw new AssertionError(e); + } + } + } + + private static class Direct extends StorableCopier { + static final Direct THE = new Direct(); + + private Direct() { + } + + public void copyAllProperties(S source, S target) { + source.copyAllProperties(target); + } + + public void copyPrimaryKeyProperties(S source, S target) { + source.copyPrimaryKeyProperties(target); + } + + public void copyVersionProperty(S source, S target) { + source.copyVersionProperty(target); + } + + public void copyUnequalProperties(S source, S target) { + source.copyUnequalProperties(target); + } + + public void copyDirtyProperties(S source, S target) { + source.copyDirtyProperties(target); + } + } + + private static class Wrapper { + private final StorableInfo mWrapperInfo; + private final StorableInfo mDelegateInfo; + private final ClassInjector mClassInjector; + private final ClassFile mClassFile; + + Wrapper(Class wrapper, Class delegate) { + mWrapperInfo = StorableIntrospector.examine(wrapper); + mDelegateInfo = StorableIntrospector.examine(delegate); + + mClassInjector = ClassInjector.create(wrapper.getName(), wrapper.getClassLoader()); + + mClassFile = CodeBuilderUtil.createStorableClassFile + (mClassInjector, mWrapperInfo.getStorableType(), + false, StorableCopier.class.getName()); + } + + Constructor generate() { + TypeDesc wrapperType = TypeDesc.forClass(mWrapperInfo.getStorableType()); + TypeDesc delegateType = TypeDesc.forClass(mDelegateInfo.getStorableType()); + TypeDesc classType = TypeDesc.forClass(Class.class); + + mClassFile.addField(Modifiers.PRIVATE.toFinal(true), "delegate", delegateType); + + MethodInfo mi = mClassFile.addConstructor + (Modifiers.PUBLIC, new TypeDesc[] {delegateType}); + CodeBuilder b = new CodeBuilder(mi); + b.loadThis(); + b.invokeSuperConstructor(null); + b.loadThis(); + b.loadLocal(b.getParameter(0)); + b.storeField("delegate", delegateType); + b.returnVoid(); + + // Implement property access methods. + for (StorableProperty wrapperProp : mWrapperInfo.getAllProperties().values()) { + if (wrapperProp.isDerived()) { + continue; + } + + TypeDesc wrapperPropType = TypeDesc.forClass(wrapperProp.getType()); + + StorableProperty delegateProp = + mDelegateInfo.getAllProperties().get(wrapperProp.getName()); + + if (delegateProp == null || delegateProp.isDerived()) { + addUnmatchedProperty(wrapperProp, wrapperPropType); + continue; + } + + TypeDesc delegatePropType = TypeDesc.forClass(delegateProp.getType()); + + if (wrapperPropType.equals(delegatePropType)) { + // No conversion required. + b = new CodeBuilder(mClassFile.addMethod(wrapperProp.getReadMethod())); + b.loadThis(); + b.loadField("delegate", delegateType); + b.invoke(delegateProp.getReadMethod()); + b.returnValue(wrapperPropType); + + b = new CodeBuilder(mClassFile.addMethod(wrapperProp.getWriteMethod())); + b.loadThis(); + b.loadField("delegate", delegateType); + b.loadLocal(b.getParameter(0)); + b.invoke(delegateProp.getWriteMethod()); + b.returnVoid(); + + continue; + } + + TypeDesc wrapperPrimPropType = wrapperPropType.toPrimitiveType(); + TypeDesc delegatePrimPropType = delegatePropType.toPrimitiveType(); + + if (wrapperPrimPropType == null || delegatePrimPropType == null) { + addUnmatchedProperty(wrapperProp, wrapperPropType); + continue; + } + + // Convert primitive or boxed type. + + b = new CodeBuilder(mClassFile.addMethod(wrapperProp.getReadMethod())); + b.loadThis(); + b.loadField("delegate", delegateType); + b.invoke(delegateProp.getReadMethod()); + if (wrapperPropType.isPrimitive() && !delegatePropType.isPrimitive()) { + // Check for null. + b.dup(); + Label notNull = b.createLabel(); + b.ifNullBranch(notNull, false); + CodeBuilderUtil.blankValue(b, wrapperPropType); + b.returnValue(wrapperPropType); + notNull.setLocation(); + } + b.convert(delegatePropType, wrapperPropType); + b.returnValue(wrapperPropType); + + b = new CodeBuilder(mClassFile.addMethod(wrapperProp.getWriteMethod())); + b.loadThis(); + b.loadField("delegate", delegateType); + b.loadLocal(b.getParameter(0)); + if (!wrapperPropType.isPrimitive() && delegatePropType.isPrimitive()) { + // Check for null. + Label notNull = b.createLabel(); + b.ifNullBranch(notNull, false); + CodeBuilderUtil.blankValue(b, delegatePropType); + b.invoke(delegateProp.getWriteMethod()); + b.returnVoid(); + notNull.setLocation(); + b.loadLocal(b.getParameter(0)); + } + b.convert(wrapperPropType, delegatePropType); + b.invoke(delegateProp.getWriteMethod()); + b.returnVoid(); + } + + try { + Class wrapperClass = mClassInjector.defineClass(mClassFile); + return wrapperClass.getConstructor(mDelegateInfo.getStorableType()); + } catch (Exception e) { + throw new AssertionError(e); + } + } + + private void addUnmatchedProperty(StorableProperty wrapperProp, + TypeDesc wrapperPropType) + { + CodeBuilder b = new CodeBuilder(mClassFile.addMethod(wrapperProp.getReadMethod())); + CodeBuilderUtil.blankValue(b, wrapperPropType); + b.returnValue(wrapperPropType); + + b = new CodeBuilder(mClassFile.addMethod(wrapperProp.getWriteMethod())); + b.returnVoid(); + } + } +} diff --git a/src/main/java/com/amazon/carbonado/layout/Layout.java b/src/main/java/com/amazon/carbonado/layout/Layout.java index 7aea758..570db8c 100644 --- a/src/main/java/com/amazon/carbonado/layout/Layout.java +++ b/src/main/java/com/amazon/carbonado/layout/Layout.java @@ -19,8 +19,11 @@ package com.amazon.carbonado.layout; import java.io.IOException; +import java.io.OutputStream; + import java.net.InetAddress; import java.net.UnknownHostException; + import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -135,7 +138,7 @@ public class Layout { private final StoredLayout mStoredLayout; private final LayoutOptions mOptions; - private transient List mAllProperties; + private volatile List mAllProperties; /** * Creates a Layout around an existing storable. @@ -248,7 +251,9 @@ public class Layout { * Returns all the properties of this layout, in their proper order. */ public List getAllProperties() throws FetchException { - if (mAllProperties == null) { + List all = mAllProperties; + + if (all == null) { Cursor cursor = mLayoutFactory.mPropertyStorage .query("layoutID = ?") .with(mStoredLayout.getLayoutID()) @@ -262,13 +267,13 @@ public class Layout { list.add(new LayoutProperty(cursor.next())); } - mAllProperties = Collections.unmodifiableList(list); + mAllProperties = all = Collections.unmodifiableList(list); } finally { cursor.close(); } } - return mAllProperties; + return all; } /** @@ -482,6 +487,32 @@ public class Layout { && getAllProperties().equals(layout.getAllProperties()); } + /** + * Write a layout to be read by {@link LayoutFactory#readLayoutFrom}. + * + * @since 1.2.2 + */ + public void writeTo(OutputStream out) throws IOException, RepositoryException { + mStoredLayout.writeTo(out); + + Cursor cursor = mLayoutFactory.mPropertyStorage + .query("layoutID = ?") + .with(mStoredLayout.getLayoutID()) + .fetch(); + + try { + while (cursor.hasNext()) { + StoredLayoutProperty prop = cursor.next(); + out.write(1); + prop.writeTo(out); + } + } finally { + cursor.close(); + } + + out.write(0); + } + // Assumes caller is in a transaction. void insert(int generation) throws PersistException { if (mAllProperties == null) { diff --git a/src/main/java/com/amazon/carbonado/layout/LayoutFactory.java b/src/main/java/com/amazon/carbonado/layout/LayoutFactory.java index 54b6933..50218e6 100644 --- a/src/main/java/com/amazon/carbonado/layout/LayoutFactory.java +++ b/src/main/java/com/amazon/carbonado/layout/LayoutFactory.java @@ -18,6 +18,9 @@ package com.amazon.carbonado.layout; +import java.io.InputStream; +import java.io.IOException; + import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; @@ -266,6 +269,51 @@ public class LayoutFactory implements LayoutCapability { return new Layout(this, storedLayout); } + /** + * Read a layout as written by {@link Layout#writeTo}. + * + * @since 1.2.2 + */ + public Layout readLayoutFrom(InputStream in) throws IOException, RepositoryException { + Transaction txn = mRepository.enterTransaction(); + try { + txn.setForUpdate(true); + StoredLayout storedLayout = mLayoutStorage.prepare(); + storedLayout.readFrom(in); + try { + storedLayout.insert(); + } catch (UniqueConstraintException e) { + StoredLayout existing = mLayoutStorage.prepare(); + storedLayout.copyPrimaryKeyProperties(existing); + if (!existing.tryLoad() || !existing.equalProperties(storedLayout)) { + throw e; + } + } + + int op; + while ((op = in.read()) != 0) { + StoredLayoutProperty storedProperty = mPropertyStorage.prepare(); + storedProperty.readFrom(in); + try { + storedProperty.insert(); + } catch (UniqueConstraintException e) { + StoredLayoutProperty existing = mPropertyStorage.prepare(); + storedProperty.copyPrimaryKeyProperties(existing); + existing.load(); + if (!existing.tryLoad() || !existing.equalProperties(storedProperty)) { + throw e; + } + } + } + + txn.commit(); + + return new Layout(this, storedLayout); + } finally { + txn.exit(); + } + } + synchronized void registerReconstructed (Class reconstructed, Layout layout) { -- cgit v1.2.3