/* * Copyright 2006-2012 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.Method; import java.math.BigDecimal; import java.util.ArrayList; import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Map; import org.cojen.classfile.ClassFile; import org.cojen.classfile.CodeBuilder; import org.cojen.classfile.Label; import org.cojen.classfile.LocalVariable; import org.cojen.classfile.MethodInfo; import org.cojen.classfile.Modifiers; import org.cojen.classfile.Opcode; import org.cojen.classfile.TypeDesc; import org.cojen.util.ClassInjector; import org.cojen.util.KeyFactory; import com.amazon.carbonado.ConstraintException; import com.amazon.carbonado.IsolationLevel; import com.amazon.carbonado.OptimisticLockException; import com.amazon.carbonado.PersistException; import com.amazon.carbonado.Repository; import com.amazon.carbonado.Storable; import com.amazon.carbonado.SupportException; 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.sequence.SequenceValueProducer; import com.amazon.carbonado.util.SoftValuedCache; import static com.amazon.carbonado.gen.CommonMethodNames.*; /** * Generates and caches abstract implementations of {@link Storable} types * suitable for use by master repositories. The generated classes extend those * generated by {@link StorableGenerator}. Subclasses need not worry about * transactions since this class takes care of that. * * @author Brian S O'Neill * @author Olga Kuznetsova * @since 1.2 */ public final class MasterStorableGenerator<S extends Storable> { // Note: All generated fields/methods have a "$" character in them to // prevent name collisions with any inherited fields/methods. User storable // properties are defined as fields which exactly match the property // name. We don't want collisions with those either. Legal bean properties // cannot have "$" in them, so there's nothing to worry about. /** Name of protected abstract method in generated storable */ public static final String DO_TRY_LOAD_MASTER_METHOD_NAME = StorableGenerator.DO_TRY_LOAD_METHOD_NAME, DO_TRY_INSERT_MASTER_METHOD_NAME = "doTryInsert$master", DO_TRY_UPDATE_MASTER_METHOD_NAME = "doTryUpdate$master", DO_TRY_DELETE_MASTER_METHOD_NAME = "doTryDelete$master"; private static final String APPEND_UNINIT_PROPERTY = "appendUninitializedPropertyName$"; private static final String INSERT_OP = "Insert"; private static final String UPDATE_OP = "Update"; private static final String DELETE_OP = "Delete"; // Cache of generated abstract classes. private static SoftValuedCache<Object, Class<? extends Storable>> cCache = SoftValuedCache.newCache(11); /** * Returns an abstract implementation of the given Storable type, which * is fully thread-safe. The Storable type itself may be an interface or * a class. If it is a class, then it must not be final, and it must have a * public, no-arg constructor. The constructor for the returned abstract * class looks like this: * * <pre> * public <init>(MasterSupport); * </pre> * * Subclasses must implement the following abstract protected methods, * whose exact names are defined by constants in this class: * * <pre> * // Load the object by examining the primary key. * protected abstract boolean doTryLoad() throws FetchException; * * // Insert the object into the storage layer. * protected abstract boolean doTryInsert_master() throws PersistException; * * // Update the object in the storage. * protected abstract boolean doTryUpdate_master() throws PersistException; * * // Delete the object from the storage layer by the primary key. * protected abstract boolean doTryDelete_master() throws PersistException; * </pre> * * Subclasses can access the MasterSupport instance via the protected field * named by {@link StorableGenerator#SUPPORT_FIELD_NAME SUPPORT_FIELD_NAME}. * * @throws com.amazon.carbonado.MalformedTypeException if Storable type is not well-formed * @throws IllegalArgumentException if type is null * @see MasterSupport */ public static <S extends Storable> Class<? extends S> getAbstractClass(Class<S> type, EnumSet<MasterFeature> features) throws SupportException, IllegalArgumentException { StorableInfo<S> info = StorableIntrospector.examine(type); if (features == null) { features = EnumSet.noneOf(MasterFeature.class); } else { features = features.clone(); } // Remove any features which don't apply. { boolean anySequences = false; boolean doNormalize = false; if (features.contains(MasterFeature.INSERT_SEQUENCES) || features.contains(MasterFeature.NORMALIZE)) { for (StorableProperty<S> property : info.getAllProperties().values()) { if (property.isDerived() || property.isJoin()) { continue; } if (!anySequences) { if (property.getSequenceName() != null) { anySequences = true; } } if (!doNormalize) { if (BigDecimal.class.isAssignableFrom(property.getType())) { doNormalize = true; } } if (anySequences && doNormalize) { break; } } if (!anySequences) { features.remove(MasterFeature.INSERT_SEQUENCES); } if (!doNormalize) { features.remove(MasterFeature.NORMALIZE); } } if (info.getVersionProperty() == null) { features.remove(MasterFeature.VERSIONING); } if (info.getPartitionKey() == null || info.getPartitionKey().getProperties().size() == 0) { features.remove(MasterFeature.PARTITIONING); } } // Add implied features. if (features.contains(MasterFeature.VERSIONING)) { // Implied feature. features.add(MasterFeature.UPDATE_FULL); } if (alwaysHasTxn(INSERT_OP, features)) { // Implied feature. features.add(MasterFeature.INSERT_TXN); } if (alwaysHasTxn(UPDATE_OP, features)) { // Implied feature. features.add(MasterFeature.UPDATE_TXN); } if (alwaysHasTxn(DELETE_OP, features)) { // Implied feature. features.add(MasterFeature.DELETE_TXN); } if (requiresTxnForUpdate(INSERT_OP, features)) { // Implied feature. features.add(MasterFeature.INSERT_TXN_FOR_UPDATE); } if (requiresTxnForUpdate(UPDATE_OP, features)) { // Implied feature. features.add(MasterFeature.UPDATE_TXN_FOR_UPDATE); } if (requiresTxnForUpdate(DELETE_OP, features)) { // Implied feature. features.add(MasterFeature.DELETE_TXN_FOR_UPDATE); } Object key = KeyFactory.createKey(new Object[] {type, features}); synchronized (cCache) { Class<? extends S> abstractClass = (Class<? extends S>) cCache.get(key); if (abstractClass != null) { return abstractClass; } abstractClass = new MasterStorableGenerator<S>(type, features).generateAndInjectClass(); cCache.put(key, abstractClass); return abstractClass; } } private final EnumSet<MasterFeature> mFeatures; private final StorableInfo<S> mInfo; private final Map<String, ? extends StorableProperty<S>> mAllProperties; private final ClassInjector mClassInjector; private final ClassFile mClassFile; private MasterStorableGenerator(Class<S> storableType, EnumSet<MasterFeature> features) { mFeatures = features; mInfo = StorableIntrospector.examine(storableType); mAllProperties = mInfo.getAllProperties(); final Class<? extends S> abstractClass = StorableGenerator.getAbstractClass(storableType); mClassInjector = ClassInjector.create (storableType.getName(), abstractClass.getClassLoader()); mClassFile = new ClassFile(mClassInjector.getClassName(), abstractClass); mClassFile.setModifiers(mClassFile.getModifiers().toAbstract(true)); mClassFile.markSynthetic(); mClassFile.setSourceFile(MasterStorableGenerator.class.getName()); mClassFile.setTarget("1.5"); } private Class<? extends S> generateAndInjectClass() throws SupportException { generateClass(); Class abstractClass = mClassInjector.defineClass(mClassFile); return (Class<? extends S>) abstractClass; } private void generateClass() throws SupportException { // Declare some types. final TypeDesc storableType = TypeDesc.forClass(Storable.class); final TypeDesc triggerSupportType = TypeDesc.forClass(TriggerSupport.class); final TypeDesc masterSupportType = TypeDesc.forClass(MasterSupport.class); final TypeDesc transactionType = TypeDesc.forClass(Transaction.class); final TypeDesc optimisticLockType = TypeDesc.forClass(OptimisticLockException.class); final TypeDesc persistExceptionType = TypeDesc.forClass(PersistException.class); // Add constructor that accepts a MasterSupport. { TypeDesc[] params = {masterSupportType}; MethodInfo mi = mClassFile.addConstructor(Modifiers.PUBLIC, params); CodeBuilder b = new CodeBuilder(mi); b.loadThis(); b.loadLocal(b.getParameter(0)); b.invokeSuperConstructor(new TypeDesc[] {triggerSupportType}); b.returnVoid(); } // Declare protected abstract methods. { MethodInfo mi = mClassFile.addMethod (Modifiers.PROTECTED.toAbstract(true), DO_TRY_INSERT_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null); mi.addException(persistExceptionType); mi = mClassFile.addMethod (Modifiers.PROTECTED.toAbstract(true), DO_TRY_UPDATE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null); mi.addException(persistExceptionType); mi = mClassFile.addMethod (Modifiers.PROTECTED.toAbstract(true), DO_TRY_DELETE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null); mi.addException(persistExceptionType); } // Add features pertaining to partitioning { if (mFeatures.contains(MasterFeature.PARTITIONING)) { // Overwrite write function for each property if Partitioning is enabled to check // that properties that are part of the Partition key are not overwritten when clean for (StorableProperty<S> property : mAllProperties.values()) { if (!property.isDerived() && property.isPartitionKeyMember()) { Method writeMethod = property.getWriteMethod(); MethodInfo mi; String writeName = property.getWriteMethodName(); final TypeDesc type = TypeDesc.forClass(property.getType()); if (writeMethod != null) { mi = mClassFile.addMethod(writeMethod); } else { continue; } CodeBuilder b = new CodeBuilder(mi); String stateFieldName = StorableGenerator.PROPERTY_STATE_FIELD_NAME + (property.getNumber() >> 4); b.loadThis(); b.loadField(stateFieldName, TypeDesc.INT); b.loadConstant(StorableGenerator.PROPERTY_STATE_MASK << ((property.getNumber() & 0xf) * 2)); b.math(Opcode.IAND); b.loadConstant(StorableGenerator.PROPERTY_STATE_CLEAN << ((property.getNumber() & 0xf) * 2)); Label isMutable = b.createLabel(); b.ifComparisonBranch(isMutable, "!="); CodeBuilderUtil.throwException (b, IllegalStateException.class, "Cannot alter partition key"); isMutable.setLocation(); b.loadThis(); b.loadLocal(b.getParameter(0)); b.invokeSuper(mClassFile.getSuperClassName(), writeName, null, new TypeDesc[]{type}); b.returnVoid(); } } } } // Add required protected doTryInsert method. { if (mFeatures.contains(MasterFeature.PARTITIONING) || mFeatures.contains(MasterFeature.INSERT_SEQUENCES) || mFeatures.contains(MasterFeature.INSERT_NO_CHECK_PRIMARY_PK)) { MethodInfo mi = mClassFile.addMethod (Modifiers.PROTECTED, StorableGenerator.CHECK_PK_FOR_INSERT_METHOD_NAME, null, null); CodeBuilder b = new CodeBuilder(mi); if (mFeatures.contains(MasterFeature.PARTITIONING)) { // Check that partition key exists in insert operation checkIfPartitionKeyPresent(b); } // If sequence support requested, implement special insert hook to // call sequences for properties which are UNINITIALIZED. User may // provide explicit values for properties with sequences. if (mFeatures.contains(MasterFeature.INSERT_SEQUENCES)) { int ordinal = 0; for (StorableProperty<S> property : mAllProperties.values()) { if (!property.isDerived() && property.getSequenceName() != null) { // Check the state of this property, to see if it is // uninitialized. Uninitialized state has value zero. String stateFieldName = StorableGenerator.PROPERTY_STATE_FIELD_NAME + (ordinal >> 4); b.loadThis(); b.loadField(stateFieldName, TypeDesc.INT); int shift = (ordinal & 0xf) * 2; b.loadConstant(StorableGenerator.PROPERTY_STATE_MASK << shift); b.math(Opcode.IAND); Label isInitialized = b.createLabel(); b.ifZeroComparisonBranch(isInitialized, "!="); // Load this in preparation for storing value to property. b.loadThis(); // Call MasterSupport.getSequenceValueProducer(String). TypeDesc seqValueProdType = TypeDesc.forClass(SequenceValueProducer.class); b.loadThis(); b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType); b.checkCast(masterSupportType); b.loadConstant(property.getSequenceName()); b.invokeInterface (masterSupportType, "getSequenceValueProducer", seqValueProdType, new TypeDesc[] {TypeDesc.STRING}); // Find appropriate method to call for getting next sequence value. TypeDesc propertyType = TypeDesc.forClass(property.getType()); TypeDesc propertyObjType = propertyType.toObjectType(); Method method; try { if (propertyObjType == TypeDesc.LONG.toObjectType()) { method = SequenceValueProducer.class .getMethod("nextLongValue", (Class[]) null); } else if (propertyObjType == TypeDesc.INT.toObjectType()) { method = SequenceValueProducer.class .getMethod("nextIntValue", (Class[]) null); } else if (propertyObjType == TypeDesc.STRING) { method = SequenceValueProducer.class .getMethod("nextDecimalValue", (Class[]) null); } else { throw new SupportException ("Unable to support sequence of type \"" + TypeDesc.forClass(property.getType()).getFullName() + "\" for property: " + property.getName()); } } catch (NoSuchMethodException e) { Error err = new NoSuchMethodError(); err.initCause(e); throw err; } b.invoke(method); b.convert(TypeDesc.forClass(method.getReturnType()), propertyType); // Store property b.storeField(property.getName(), propertyType); // Set state to dirty. b.loadThis(); b.loadThis(); b.loadField(stateFieldName, TypeDesc.INT); b.loadConstant(StorableGenerator.PROPERTY_STATE_DIRTY << shift); b.math(Opcode.IOR); b.storeField(stateFieldName, TypeDesc.INT); isInitialized.setLocation(); } ordinal++; } // We've tried our best to fill in missing values, so now // run the original check method, if required to. } if (!mFeatures.contains(MasterFeature.INSERT_NO_CHECK_PRIMARY_PK)) { b.loadThis(); b.invokeSuper(mClassFile.getSuperClassName(), StorableGenerator.CHECK_PK_FOR_INSERT_METHOD_NAME, null, null); } b.returnVoid(); } MethodInfo mi = mClassFile.addMethod (Modifiers.PROTECTED.toFinal(true), StorableGenerator.DO_TRY_INSERT_METHOD_NAME, TypeDesc.BOOLEAN, null); mi.addException(persistExceptionType); CodeBuilder b = new CodeBuilder(mi); LocalVariable txnVar = b.createLocalVariable(null, transactionType); Label tryStart = addEnterTransaction(b, INSERT_OP, txnVar); LocalVariable wasVersionInitVar = null; if (mFeatures.contains(MasterFeature.VERSIONING)) { if (!mInfo.getVersionProperty().isDerived()) { // Only set if uninitialized. b.loadThis(); b.invokeVirtual(StorableGenerator.IS_VERSION_INITIALIZED_METHOD_NAME, TypeDesc.BOOLEAN, null); wasVersionInitVar = b.createLocalVariable(null, TypeDesc.BOOLEAN); b.storeLocal(wasVersionInitVar); b.loadLocal(wasVersionInitVar); Label isInitialized = b.createLabel(); b.ifZeroComparisonBranch(isInitialized, "!="); addAdjustVersionProperty(b, null, 1); isInitialized.setLocation(); } } if (mFeatures.contains(MasterFeature.INSERT_CHECK_REQUIRED)) { // Ensure that required properties have been set. b.loadThis(); b.invokeVirtual(StorableGenerator.IS_REQUIRED_DATA_INITIALIZED_METHOD_NAME, TypeDesc.BOOLEAN, null); Label isInitialized = b.createLabel(); b.ifZeroComparisonBranch(isInitialized, "!="); // Throw a ConstraintException. TypeDesc exType = TypeDesc.forClass(ConstraintException.class); b.newObject(exType); b.dup(); // Append all the uninitialized property names to the exception message. LocalVariable countVar = b.createLocalVariable(null, TypeDesc.INT); b.loadConstant(0); b.storeLocal(countVar); TypeDesc sbType = TypeDesc.forClass(StringBuilder.class); b.newObject(sbType); b.dup(); b.loadConstant("Not all required properties have been set: "); TypeDesc[] stringParam = {TypeDesc.STRING}; b.invokeConstructor(sbType, stringParam); LocalVariable sbVar = b.createLocalVariable(null, sbType); b.storeLocal(sbVar); int ordinal = -1; HashSet<Integer> stateAppendMethods = new HashSet<Integer>(); // Parameters are: StringBuilder, count, mask, property name TypeDesc[] appendParams = {sbType, TypeDesc.INT, TypeDesc.INT, TypeDesc.STRING}; for (StorableProperty<S> property : mAllProperties.values()) { ordinal++; if (property.isDerived() || property.isIndependent() || property.isJoin() || property.isPrimaryKeyMember() || property.isNullable() || property.isAutomatic() || property.isVersion()) { continue; } int stateField = ordinal >> 4; String stateAppendMethodName = APPEND_UNINIT_PROPERTY + stateField; if (!stateAppendMethods.contains(stateField)) { stateAppendMethods.add(stateField); MethodInfo mi2 = mClassFile.addMethod (Modifiers.PRIVATE, stateAppendMethodName, TypeDesc.INT, appendParams); CodeBuilder b2 = new CodeBuilder(mi2); // Load the StringBuilder parameter. b2.loadLocal(b2.getParameter(0)); String stateFieldName = StorableGenerator.PROPERTY_STATE_FIELD_NAME + (ordinal >> 4); b2.loadThis(); b2.loadField(stateFieldName, TypeDesc.INT); // Load the mask parameter. b2.loadLocal(b2.getParameter(2)); b2.math(Opcode.IAND); Label propIsInitialized = b2.createLabel(); b2.ifZeroComparisonBranch(propIsInitialized, "!="); // Load the count parameter. b2.loadLocal(b2.getParameter(1)); Label noComma = b2.createLabel(); b2.ifZeroComparisonBranch(noComma, "=="); b2.loadConstant(", "); b2.invokeVirtual(sbType, "append", sbType, stringParam); noComma.setLocation(); // Load the property name parameter. b2.loadLocal(b2.getParameter(3)); b2.invokeVirtual(sbType, "append", sbType, stringParam); // Increment the count parameter. b2.integerIncrement(b2.getParameter(1), 1); propIsInitialized.setLocation(); // Return the possibly updated count. b2.loadLocal(b2.getParameter(1)); b2.returnValue(TypeDesc.INT); } b.loadThis(); // Parameters are: StringBuilder, count, mask, property name b.loadLocal(sbVar); b.loadLocal(countVar); b.loadConstant(StorableGenerator.PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2)); b.loadConstant(property.getName()); b.invokePrivate(stateAppendMethodName, TypeDesc.INT, appendParams); b.storeLocal(countVar); } b.loadLocal(sbVar); b.invokeVirtual(sbType, "toString", TypeDesc.STRING, null); b.invokeConstructor(exType, new TypeDesc[] {TypeDesc.STRING}); b.throwObject(); isInitialized.setLocation(); } // Copy of properties before normalization. List<PropertyCopy> unnormalized = addNormalization(b, false); Label doTryStart = b.createLabel().setLocation(); b.loadThis(); b.invokeVirtual(DO_TRY_INSERT_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null); if (wasVersionInitVar != null) { // Decide if version property needs to rollback to uninitialized. b.dup(); Label noRollback = b.createLabel(); // Don't rollback if insert succeeded. b.ifZeroComparisonBranch(noRollback, "!="); b.loadLocal(wasVersionInitVar); // Rollback only if version was automatically set. b.ifZeroComparisonBranch(noRollback, "!="); unsetVersionProperty(b); noRollback.setLocation(); } addNormalizationRollback(b, doTryStart, unnormalized); if (tryStart == null) { b.returnValue(TypeDesc.BOOLEAN); } else { Label failed = b.createLabel(); b.ifZeroComparisonBranch(failed, "=="); addCommitAndExitTransaction(b, INSERT_OP, txnVar); b.loadConstant(true); b.returnValue(TypeDesc.BOOLEAN); failed.setLocation(); addExitTransaction(b, INSERT_OP, txnVar); b.loadConstant(false); b.returnValue(TypeDesc.BOOLEAN); addExitTransaction(b, INSERT_OP, txnVar, tryStart); } } // Add required protected doTryUpdate method. addDoTryUpdate: { if (mFeatures.contains(MasterFeature.PARTITIONING)) { // Check that partition key exists in update operation MethodInfo mi = mClassFile.addMethod (Modifiers.PROTECTED, StorableGenerator.CHECK_PK_FOR_UPDATE_METHOD_NAME, null, null); CodeBuilder b = new CodeBuilder(mi); checkIfPartitionKeyPresent(b); b.loadThis(); b.invokeSuper(mClassFile.getSuperClassName(), StorableGenerator.CHECK_PK_FOR_UPDATE_METHOD_NAME, null, null); b.returnVoid(); } MethodInfo mi = mClassFile.addMethod (Modifiers.PROTECTED.toFinal(true), StorableGenerator.DO_TRY_UPDATE_METHOD_NAME, TypeDesc.BOOLEAN, null); mi.addException(persistExceptionType); CodeBuilder b = new CodeBuilder(mi); if ((!mFeatures.contains(MasterFeature.VERSIONING)) && (!mFeatures.contains(MasterFeature.NORMALIZE)) && (!mFeatures.contains(MasterFeature.UPDATE_FULL)) && (!mFeatures.contains(MasterFeature.UPDATE_TXN))) { // Nothing special needs to be done, so just delegate and return. b.loadThis(); b.invokeVirtual(DO_TRY_UPDATE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null); b.returnValue(TypeDesc.BOOLEAN); break addDoTryUpdate; } LocalVariable txnVar = b.createLocalVariable(null, transactionType); LocalVariable savedVar = null; Label tryStart = addEnterTransaction(b, UPDATE_OP, txnVar); Label failed = b.createLabel(); Label tryLoadStart = null, tryLoadEnd = null; if (!mFeatures.contains(MasterFeature.UPDATE_FULL)) { // Copy of properties before normalization. List<PropertyCopy> unnormalized = addNormalization(b, true); Label doTryStart = b.createLabel().setLocation(); // if (!this.doTryUpdateMaster()) { // goto failed; // } b.loadThis(); b.invokeVirtual(DO_TRY_UPDATE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null); addNormalizationRollback(b, doTryStart, unnormalized); b.ifZeroComparisonBranch(failed, "=="); } else { // Storable saved = copy(); b.loadThis(); b.invokeVirtual(COPY_METHOD_NAME, storableType, null); b.checkCast(mClassFile.getType()); savedVar = b.createLocalVariable(null, mClassFile.getType()); b.storeLocal(savedVar); // support.locallyDisableLoadTrigger(); // try { // if (!saved.tryLoad()) { // goto failed; // } // } finally { // support.locallyEnableLoadTrigger(); // } b.loadThis(); b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType); b.invokeInterface(triggerSupportType, "locallyDisableLoadTrigger", null, null); tryLoadStart = b.createLabel().setLocation(); b.loadLocal(savedVar); b.invokeInterface(storableType, TRY_LOAD_METHOD_NAME, TypeDesc.BOOLEAN, null); tryLoadEnd = b.createLabel().setLocation(); b.loadThis(); b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType); b.invokeInterface(triggerSupportType, "locallyEnableLoadTrigger", null, null); b.ifZeroComparisonBranch(failed, "=="); // Exception handler generated at the end of method. // if (version support enabled) { // if (!derived version) { // if (this.getVersionNumber() != saved.getVersionNumber()) { // throw new OptimisticLockException // (this.getVersionNumber(), saved.getVersionNumber(), this); // } // } else { // if (this.getVersionNumber() <= saved.getVersionNumber()) { // throw new OptimisticLockException // (saved.getVersionNumber(), this, this.getVersionNumber()); // } // } // } if (mFeatures.contains(MasterFeature.VERSIONING)) { StorableProperty<S> versionProperty = mInfo.getVersionProperty(); TypeDesc versionType = TypeDesc.forClass(versionProperty.getType()); Label allowedVersion = b.createLabel(); if (!versionProperty.isDerived()) { b.loadThis(); b.invoke(versionProperty.getReadMethod()); b.loadLocal(savedVar); b.invoke(versionProperty.getReadMethod()); CodeBuilderUtil.addValuesEqualCall (b, versionType, true, allowedVersion, true); b.newObject(optimisticLockType); b.dup(); b.loadThis(); b.invoke(versionProperty.getReadMethod()); b.convert(versionType, TypeDesc.OBJECT); b.loadLocal(savedVar); b.invoke(versionProperty.getReadMethod()); b.convert(versionType, TypeDesc.OBJECT); b.loadThis(); b.invokeConstructor (optimisticLockType, new TypeDesc[] {TypeDesc.OBJECT, TypeDesc.OBJECT, storableType}); b.throwObject(); } else { b.loadThis(); b.invoke(versionProperty.getReadMethod()); LocalVariable newVersion = b.createLocalVariable(null, versionType); b.storeLocal(newVersion); b.loadLocal(savedVar); b.invoke(versionProperty.getReadMethod()); LocalVariable savedVersion = b.createLocalVariable(null, versionType); b.storeLocal(savedVersion); // Skip check if new or saved version is null. branchIfNull(b, newVersion, allowedVersion); branchIfNull(b, savedVersion, allowedVersion); TypeDesc primVersionType = versionType.toPrimitiveType(); if (primVersionType != null) { if (versionType != primVersionType) { b.loadLocal(newVersion); b.convert(versionType, primVersionType); newVersion = b.createLocalVariable(null, primVersionType); b.storeLocal(newVersion); b.loadLocal(savedVersion); b.convert(versionType, primVersionType); savedVersion = b.createLocalVariable(null, primVersionType); b.storeLocal(savedVersion); } // Skip check if new or saved version is NaN. branchIfNaN(b, newVersion, allowedVersion); branchIfNaN(b, savedVersion, allowedVersion); b.loadLocal(newVersion); b.loadLocal(savedVersion); b.ifComparisonBranch(allowedVersion, ">", primVersionType); } else if (Comparable.class.isAssignableFrom(versionProperty.getType())) { b.loadLocal(newVersion); b.loadLocal(savedVersion); b.invokeInterface(TypeDesc.forClass(Comparable.class), "compareTo", TypeDesc.INT, new TypeDesc[] {TypeDesc.OBJECT}); b.ifZeroComparisonBranch(allowedVersion, ">"); } else { throw new SupportException ("Derived version property must be Comparable: " + versionProperty); } b.newObject(optimisticLockType); b.dup(); b.loadLocal(savedVar); b.invoke(versionProperty.getReadMethod()); b.convert(versionType, TypeDesc.OBJECT); b.loadThis(); b.loadThis(); b.invoke(versionProperty.getReadMethod()); b.convert(versionType, TypeDesc.OBJECT); b.invokeConstructor (optimisticLockType, new TypeDesc[] {TypeDesc.OBJECT, storableType, TypeDesc.OBJECT}); b.throwObject(); } allowedVersion.setLocation(); } // Copy of properties before normalization. List<PropertyCopy> unnormalized = addNormalization(b, true); Label doTryStart = b.createLabel().setLocation(); // this.copyDirtyProperties(saved); // if (version support enabled) { // saved.setVersionNumber(saved.getVersionNumber() + 1); // } b.loadThis(); b.loadLocal(savedVar); b.invokeVirtual(COPY_DIRTY_PROPERTIES, null, new TypeDesc[] {storableType}); if (mFeatures.contains(MasterFeature.VERSIONING)) { if (!mInfo.getVersionProperty().isDerived()) { addAdjustVersionProperty(b, savedVar, -1); } } // if (!saved.doTryUpdateMaster()) { // goto failed; // } b.loadLocal(savedVar); b.invokeVirtual(DO_TRY_UPDATE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null); addNormalizationRollback(b, doTryStart, unnormalized); b.ifZeroComparisonBranch(failed, "=="); // saved.copyUnequalProperties(this); b.loadLocal(savedVar); b.loadThis(); b.invokeInterface (storableType, COPY_UNEQUAL_PROPERTIES, null, new TypeDesc[] {storableType}); } // txn.commit(); // txn.exit(); // return true; addCommitAndExitTransaction(b, UPDATE_OP, txnVar); b.loadConstant(true); b.returnValue(TypeDesc.BOOLEAN); // failed: // txn.exit(); failed.setLocation(); addExitTransaction(b, UPDATE_OP, txnVar); // return false; b.loadConstant(false); b.returnValue(TypeDesc.BOOLEAN); if (tryLoadStart != null) { b.exceptionHandler(tryLoadStart, tryLoadEnd, null); b.loadThis(); b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType); b.invokeInterface(triggerSupportType, "locallyEnableLoadTrigger", null, null); b.throwObject(); } addExitTransaction(b, UPDATE_OP, txnVar, tryStart); } // Add required protected doTryDelete method. { if (mFeatures.contains(MasterFeature.PARTITIONING)) { // Check that partition key exists in delete operation MethodInfo mi = mClassFile.addMethod (Modifiers.PROTECTED, StorableGenerator.CHECK_PK_FOR_DELETE_METHOD_NAME, null, null); CodeBuilder b = new CodeBuilder(mi); checkIfPartitionKeyPresent(b); b.loadThis(); b.invokeSuper(mClassFile.getSuperClassName(), StorableGenerator.CHECK_PK_FOR_DELETE_METHOD_NAME, null, null); b.returnVoid(); } MethodInfo mi = mClassFile.addMethod (Modifiers.PROTECTED.toFinal(true), StorableGenerator.DO_TRY_DELETE_METHOD_NAME, TypeDesc.BOOLEAN, null); mi.addException(persistExceptionType); CodeBuilder b = new CodeBuilder(mi); LocalVariable txnVar = b.createLocalVariable(null, transactionType); Label tryStart = addEnterTransaction(b, DELETE_OP, txnVar); b.loadThis(); b.invokeVirtual(DO_TRY_DELETE_MASTER_METHOD_NAME, TypeDesc.BOOLEAN, null); if (tryStart == null) { b.returnValue(TypeDesc.BOOLEAN); } else { Label failed = b.createLabel(); b.ifZeroComparisonBranch(failed, "=="); addCommitAndExitTransaction(b, DELETE_OP, txnVar); b.loadConstant(true); b.returnValue(TypeDesc.BOOLEAN); failed.setLocation(); addExitTransaction(b, DELETE_OP, txnVar); b.loadConstant(false); b.returnValue(TypeDesc.BOOLEAN); addExitTransaction(b, DELETE_OP, txnVar, tryStart); } } // Check that partition key exists for Load { if (mFeatures.contains(MasterFeature.PARTITIONING)) { MethodInfo mi = mClassFile.addMethod (Modifiers.PROTECTED, StorableGenerator.CHECK_PK_FOR_LOAD_METHOD_NAME, null, null); CodeBuilder b = new CodeBuilder(mi); checkIfPartitionKeyPresent(b); b.loadThis(); b.invokeSuper(mClassFile.getSuperClassName(), StorableGenerator.CHECK_PK_FOR_LOAD_METHOD_NAME, null, null); b.returnVoid(); } } } private void branchIfNull(CodeBuilder b, LocalVariable value, Label isNull) { if (!value.getType().isPrimitive()) { b.loadLocal(value); b.ifNullBranch(isNull, true); } } private void branchIfNaN(CodeBuilder b, LocalVariable value, Label isNaN) { TypeDesc type = value.getType(); if (type == TypeDesc.FLOAT || type == TypeDesc.DOUBLE) { b.loadLocal(value); if (type == TypeDesc.FLOAT) { b.invokeStatic(TypeDesc.FLOAT.toObjectType(), "isNaN", TypeDesc.BOOLEAN, new TypeDesc[] {TypeDesc.FLOAT}); b.ifZeroComparisonBranch(isNaN, "!="); } else { b.invokeStatic(TypeDesc.DOUBLE.toObjectType(), "isNaN", TypeDesc.BOOLEAN, new TypeDesc[] {TypeDesc.DOUBLE}); b.ifZeroComparisonBranch(isNaN, "!="); } } } /** * Generates code to enter a transaction, if required and if none in progress. * * @param opType type of operation, Insert, Update, or Delete * @param txnVar required variable of type Transaction for storing transaction * @return optional try start label for transaction */ private Label addEnterTransaction(CodeBuilder b, String opType, LocalVariable txnVar) { if (!alwaysHasTxn(opType)) { return null; } // Repository repo = masterSupport.getRootRepository(); TypeDesc repositoryType = TypeDesc.forClass(Repository.class); TypeDesc transactionType = TypeDesc.forClass(Transaction.class); TypeDesc triggerSupportType = TypeDesc.forClass(TriggerSupport.class); TypeDesc masterSupportType = TypeDesc.forClass(MasterSupport.class); TypeDesc isolationLevelType = TypeDesc.forClass(IsolationLevel.class); b.loadThis(); b.loadField(StorableGenerator.SUPPORT_FIELD_NAME, triggerSupportType); b.invokeInterface(masterSupportType, "getRootRepository", repositoryType, null); if (requiresTxnForUpdate(opType)) { // Always create nested transaction. // txn = repo.enterTransaction(); // txn.setForUpdate(true); b.invokeInterface(repositoryType, ENTER_TRANSACTION_METHOD_NAME, transactionType, null); b.storeLocal(txnVar); b.loadLocal(txnVar); b.loadConstant(true); b.invokeInterface(transactionType, SET_FOR_UPDATE_METHOD_NAME, null, new TypeDesc[] {TypeDesc.BOOLEAN}); } else { LocalVariable repoVar = b.createLocalVariable(null, repositoryType); b.storeLocal(repoVar); // if (repo.getTransactionIsolationLevel() != null) { // txn = null; // } else { // txn = repo.enterTransaction(); // } b.loadLocal(repoVar); b.invokeInterface(repositoryType, GET_TRANSACTION_ISOLATION_LEVEL_METHOD_NAME, isolationLevelType, null); Label notInTxn = b.createLabel(); b.ifNullBranch(notInTxn, true); b.loadNull(); Label storeTxn = b.createLabel(); b.branch(storeTxn); notInTxn.setLocation(); b.loadLocal(repoVar); b.invokeInterface(repositoryType, ENTER_TRANSACTION_METHOD_NAME, transactionType, null); storeTxn.setLocation(); b.storeLocal(txnVar); } return b.createLabel().setLocation(); } private boolean alwaysHasTxn(String opType) { return alwaysHasTxn(opType, mFeatures); } private static boolean alwaysHasTxn(String opType, EnumSet<MasterFeature> features) { if (opType == UPDATE_OP) { return features.contains(MasterFeature.UPDATE_TXN) || features.contains(MasterFeature.UPDATE_TXN_FOR_UPDATE) || features.contains(MasterFeature.VERSIONING) || features.contains(MasterFeature.UPDATE_FULL); } else if (opType == INSERT_OP) { return features.contains(MasterFeature.INSERT_TXN) || features.contains(MasterFeature.INSERT_TXN_FOR_UPDATE); } else if (opType == DELETE_OP) { return features.contains(MasterFeature.DELETE_TXN) || features.contains(MasterFeature.DELETE_TXN_FOR_UPDATE); } return false; } private boolean requiresTxnForUpdate(String opType) { return requiresTxnForUpdate(opType, mFeatures); } private static boolean requiresTxnForUpdate(String opType, EnumSet<MasterFeature> features) { if (opType == UPDATE_OP) { return features.contains(MasterFeature.UPDATE_TXN_FOR_UPDATE) || features.contains(MasterFeature.VERSIONING) || features.contains(MasterFeature.UPDATE_FULL); } else if (opType == INSERT_OP) { return features.contains(MasterFeature.INSERT_TXN_FOR_UPDATE); } else if (opType == DELETE_OP) { return features.contains(MasterFeature.DELETE_TXN_FOR_UPDATE); } return false; } private void addCommitAndExitTransaction(CodeBuilder b, String opType, LocalVariable txnVar) { if (!alwaysHasTxn(opType)) { return; } TypeDesc transactionType = TypeDesc.forClass(Transaction.class); Label noTxn = b.createLabel(); if (!requiresTxnForUpdate(opType)) { // Might be null, if transaction was already in progress. If // requires transaction for update, then a new transaction is // always created. b.loadLocal(txnVar); b.ifNullBranch(noTxn, true); } // txn.commit(); // txn.exit(); b.loadLocal(txnVar); b.invokeInterface(transactionType, COMMIT_METHOD_NAME, null, null); b.loadLocal(txnVar); b.invokeInterface(transactionType, EXIT_METHOD_NAME, null, null); noTxn.setLocation(); } /** * * @param opType type of operation, Insert, Update, or Delete */ private void addExitTransaction(CodeBuilder b, String opType, LocalVariable txnVar) { if (!alwaysHasTxn(opType)) { return; } TypeDesc transactionType = TypeDesc.forClass(Transaction.class); Label noTxn = b.createLabel(); if (!requiresTxnForUpdate(opType)) { // Might be null, if transaction was already in progress. If // requires transaction for update, then a new transaction is // always created. b.loadLocal(txnVar); b.ifNullBranch(noTxn, true); } // txn.exit(); b.loadLocal(txnVar); b.invokeInterface(transactionType, EXIT_METHOD_NAME, null, null); noTxn.setLocation(); } /** * * @param opType type of operation, Insert, Update, or Delete */ private void addExitTransaction(CodeBuilder b, String opType, LocalVariable txnVar, Label tryStart) { if (tryStart == null) { addExitTransaction(b, opType, txnVar); return; } // } catch (... e) { // txn.exit(); // throw e; // } Label tryEnd = b.createLabel().setLocation(); b.exceptionHandler(tryStart, tryEnd, null); addExitTransaction(b, opType, txnVar); b.throwObject(); } /* * Generates code to adjust the version property. If value parameter is negative, then * version is incremented as follows: * * storable.setVersionNumber(storable.getVersionNumber() + 1); * * Otherwise, the version is set: * * storable.setVersionNumber(value); * * @param storableVar references storable instance, or null if this * @param value if negative, increment version, else, set version to this value */ private void addAdjustVersionProperty(CodeBuilder b, LocalVariable storableVar, int value) throws SupportException { // Push storable to stack in preparation for calling set method below. if (storableVar == null) { b.loadThis(); } else { b.loadLocal(storableVar); } StorableProperty<?> versionProperty = mInfo.getVersionProperty(); TypeDesc versionType = TypeDesc.forClass(versionProperty.getType()); if (value >= 0) { CodeBuilderUtil.initialVersion(b, versionType, value); } else { // Load current property value. b.dup(); b.invoke(versionProperty.getReadMethod()); CodeBuilderUtil.incrementVersion(b, versionType); } b.invoke(versionProperty.getWriteMethod()); } /** * Sets the version property to its initial uninitialized state. */ private void unsetVersionProperty(CodeBuilder b) throws SupportException { StorableProperty<?> property = mInfo.getVersionProperty(); // Set the property state to uninitialized. { String stateFieldName = StorableGenerator.PROPERTY_STATE_FIELD_NAME + (property.getNumber() >> 4); b.loadThis(); b.loadThis(); b.loadField(stateFieldName, TypeDesc.INT); int shift = (property.getNumber() & 0xf) * 2; b.loadConstant(~(StorableGenerator.PROPERTY_STATE_MASK << shift)); b.math(Opcode.IAND); b.storeField(stateFieldName, TypeDesc.INT); } // Zero the property value. TypeDesc type = TypeDesc.forClass(property.getType()); b.loadThis(); CodeBuilderUtil.blankValue(b, type); b.storeField(property.getName(), type); } private List<PropertyCopy> addNormalization(CodeBuilder b, boolean forUpdate) { List<PropertyCopy> unnormalized = null; if (!mFeatures.contains(MasterFeature.NORMALIZE)) { return unnormalized; } for (StorableProperty<S> property : mAllProperties.values()) { if (property.isDerived() || property.isJoin()) { continue; } if (!BigDecimal.class.isAssignableFrom(property.getType())) { continue; } if (unnormalized == null) { unnormalized = new ArrayList<PropertyCopy>(); } PropertyCopy copy = new PropertyCopy<S>(b, property); unnormalized.add(copy); copy.makeCopy(b); // Skip property if null. b.loadLocal(copy.copyVar); Label skipNormalize = b.createLabel(); b.ifNullBranch(skipNormalize, true); if (forUpdate) { // Also skip property if not dirty. String stateFieldName = StorableGenerator.PROPERTY_STATE_FIELD_NAME + (property.getNumber() >> 4); b.loadThis(); b.loadField(stateFieldName, TypeDesc.INT); int shift = (property.getNumber() & 0xf) * 2; b.loadConstant(StorableGenerator.PROPERTY_STATE_MASK << shift); b.math(Opcode.IAND); b.loadConstant(StorableGenerator.PROPERTY_STATE_DIRTY << shift); b.ifComparisonBranch(skipNormalize, "!="); } // Normalize by stripping trailing zeros and also workaround // BigDecimal.ZERO bug when calling stripTrailingZeros. #6480539 // Load this in preparation for storing to field. b.loadThis(); TypeDesc propertyType = copy.copyVar.getType(); b.loadStaticField(propertyType, "ZERO", propertyType); b.loadLocal(copy.copyVar); b.invokeVirtual(propertyType, "compareTo", TypeDesc.INT, new TypeDesc[] {propertyType}); Label notZero = b.createLabel(); b.ifZeroComparisonBranch(notZero, "!="); b.loadStaticField(propertyType, "ZERO", propertyType); Label storeField = b.createLabel(); b.branch(storeField); notZero.setLocation(); b.loadLocal(copy.copyVar); b.invokeVirtual(propertyType, "stripTrailingZeros", propertyType, null); storeField.setLocation(); b.storeField(property.getName(), propertyType); skipNormalize.setLocation(); } return unnormalized; } /** * Assumes a boolean is on the stack, as returned by doTryInsert or doTryUpdate. */ private void addNormalizationRollback(CodeBuilder b, Label doTryStart, List<PropertyCopy> unnormalized) { if (unnormalized == null) { return; } Label doTryEnd = b.createLabel().setLocation(); b.dup(); Label success = b.createLabel(); b.ifZeroComparisonBranch(success, "!="); for (int i=0; i<2; i++) { if (i == 0) { } else { b.exceptionHandler(doTryStart, doTryEnd, null); } // Rollback normalized properties. for (PropertyCopy copy : unnormalized) { copy.restore(b); } if (i == 0) { b.branch(success); } else { b.throwObject(); } } success.setLocation(); } private void checkIfPartitionKeyPresent(CodeBuilder b) { b.loadThis(); b.invokeVirtual(StorableGenerator.IS_PARTITION_KEY_INITIALIZED_METHOD_NAME, TypeDesc.BOOLEAN, null); Label ptnkInitialized = b.createLabel(); b.ifZeroComparisonBranch(ptnkInitialized, "!="); TypeDesc exType = TypeDesc.forClass(IllegalStateException.class); b.newObject(exType); b.dup(); b.loadConstant("Partition key not fully specified"); b.invokeConstructor(exType, new TypeDesc[] {TypeDesc.STRING}); b.throwObject(); ptnkInitialized.setLocation(); } private static class PropertyCopy<S extends Storable> { final StorableProperty<S> property; final LocalVariable copyVar; PropertyCopy(CodeBuilder b, StorableProperty<S> property) { this.property = property; copyVar = b.createLocalVariable(null, TypeDesc.forClass(property.getType())); } void makeCopy(CodeBuilder b) { b.loadThis(); b.loadField(property.getName(), copyVar.getType()); b.storeLocal(copyVar); } void restore(CodeBuilder b) { b.loadThis(); b.loadLocal(copyVar); b.storeField(property.getName(), copyVar.getType()); } } }