com.amazon.carbonado.layout.LayoutFactory.java Source code

Java tutorial

Introduction

Here is the source code for com.amazon.carbonado.layout.LayoutFactory.java

Source

/*
 * 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.layout;

import java.io.InputStream;
import java.io.IOException;

import java.lang.annotation.Annotation;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import java.util.Arrays;
import java.util.Map;

import org.apache.commons.logging.LogFactory;

import com.amazon.carbonado.Cursor;
import com.amazon.carbonado.FetchDeadlockException;
import com.amazon.carbonado.FetchException;
import com.amazon.carbonado.FetchInterruptedException;
import com.amazon.carbonado.FetchNoneException;
import com.amazon.carbonado.FetchTimeoutException;
import com.amazon.carbonado.IsolationLevel;
import com.amazon.carbonado.PersistDeadlockException;
import com.amazon.carbonado.PersistException;
import com.amazon.carbonado.PersistTimeoutException;
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.UniqueConstraintException;

import com.amazon.carbonado.capability.ResyncCapability;

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;

import com.amazon.carbonado.util.SoftValuedCache;

/**
 * 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 SoftValuedCache<Class<? extends Storable>, Layout> mReconstructed;

    // Added this to allow consumers of the the main Carbonado to deal which
    // changes to how LayoutFactories are reconstructed from streams.
    public static final int VERSION = 2;

    /**
     * @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 {
        return layoutFor(type, null);
    }

    /**
     * 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, LayoutOptions options)
            throws FetchException, PersistException {
        return layoutFor(false, type, options);
    }

    /**
     * Returns the layout matching the current definition of the given type.
     *
     * @param readOnly if true, don't attempt to persist new generation because
     * underlying repository is read-only
     * @throws PersistException if type represents a new generation, but
     * persisting this information failed
     */
    public Layout layoutFor(final boolean readOnly, final Class<? extends Storable> type,
            final LayoutOptions options) throws FetchException, PersistException {
        if (options != null) {
            // Make side-effect consistently applied.
            options.readOnly();
        }

        synchronized (this) {
            if (mReconstructed != null) {
                Layout layout = mReconstructed.get(type);
                if (layout != null) {
                    return layout;
                }
            }
        }

        final StorableInfo<?> info = StorableIntrospector.examine(type);

        // Launch layout request in a new separate thread to ensure that
        // transaction is top-level.

        class LayoutRequest extends Thread {
            private boolean done;
            private Layout layout;
            private FetchException fetchEx;
            private PersistException persistEx;

            public synchronized void run() {
                try {
                    try {
                        layout = layoutFor(readOnly, info, options);
                    } catch (FetchException e) {
                        fetchEx = e;
                    } catch (PersistException e) {
                        persistEx = e;
                    }
                } finally {
                    done = true;
                    notifyAll();
                }
            }

            synchronized Layout getLayout() throws FetchException, PersistException, InterruptedException {
                while (!done) {
                    wait();
                }
                if (fetchEx != null) {
                    // Wrap to get complete stack trace.
                    throw new FetchException(fetchEx);
                }
                if (persistEx != null) {
                    // Wrap to get complete stack trace.
                    throw new PersistException(persistEx);
                }
                return layout;
            }
        }

        LayoutRequest request = new LayoutRequest();
        request.setDaemon(true);
        request.start();

        try {
            return request.getLayout();
        } catch (InterruptedException e) {
            throw new FetchInterruptedException();
        }
    }

    private Layout layoutFor(boolean readOnly, StorableInfo<?> info, LayoutOptions options)
            throws FetchException, PersistException {
        Layout layout;
        ResyncCapability resyncCap = null;

        // Try to insert metadata up to three times.
        boolean top = true;
        loadLayout: for (int retryCount = 3;;) {
            try {
                Transaction txn;
                if (top) {
                    txn = mRepository.enterTopTransaction(IsolationLevel.READ_COMMITTED);
                } else {
                    txn = mRepository.enterTransaction(IsolationLevel.READ_COMMITTED);
                }

                txn.setForUpdate(!readOnly);
                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, options, HASH_MULTIPLIERS[i]);

                        // Initially use for comparison purposes.
                        newLayout = new Layout(this, info, options, 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.
                            layout = knownLayout;
                            break loadLayout;
                        }

                        if (knownLayout.getAllProperties().size() == 0) {
                            // This is clearly wrong. All Storables must have
                            // at least one property. Assume that layout record
                            // is corrupt so rebuild it.
                            break;
                        }

                        // 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 for " + info.getName());
                        }
                    }

                    // If this point is reached, then type represents a new
                    // generation. Calculate next generation value and insert.

                    // Note: The following query might find a record that
                    // didn't exist just a moment ago. This will cause a new
                    // generation value to be calculated, which is incorrect.
                    // Inserting the layout causes a unique constraint
                    // exception, which prevents the mistake from persisting.

                    assert (newLayout != null);
                    int generation = nextGeneration(mRepository, info.getStorableType().getName());

                    newLayout.insert(readOnly, generation);
                    layout = newLayout;

                    if (generation == 0) {
                        LogFactory.getLog(getClass()).debug("New schema layout inserted: " + layout);
                    }

                    txn.commit();
                } finally {
                    txn.exit();
                }

                break;
            } catch (UniqueConstraintException e) {
                // This might be caused by a transient replication error. Retry
                // a few times before throwing exception. Wait up to a second
                // before each retry.
                retryCount = e.backoff(e, retryCount, 1000);
                resyncCap = mRepository.getCapability(ResyncCapability.class);
            } catch (FetchException e) {
                if (e instanceof FetchDeadlockException || e instanceof FetchTimeoutException) {
                    // Might be caused by coarse locks. Switch to nested
                    // transaction to share the locks.
                    if (top) {
                        top = false;
                        retryCount = e.backoff(e, retryCount, 100);
                        continue;
                    }
                }
                throw e;
            } catch (PersistException e) {
                if (e instanceof PersistDeadlockException || e instanceof PersistTimeoutException) {
                    // Might be caused by coarse locks. Switch to nested
                    // transaction to share the locks.
                    if (top) {
                        top = false;
                        retryCount = e.backoff(e, retryCount, 100);
                        continue;
                    }
                }
                throw e;
            }
        }

        if (!readOnly && resyncCap != null) {
            // Make sure that all layout records are sync'd.
            try {
                resyncCap.resync(StoredLayoutProperty.class, 1.0, null);
            } catch (RepositoryException e) {
                throw e.toPersistException();
            }
        }

        return layout;
    }

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

    /**
     * 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()) {
                    // Only check subset of primary and alternate keys. The check
                    // of layout properties is more important.
                    if (!existing.getStorableTypeName().equals(storedLayout.getStorableTypeName())) {
                        throw e;
                    }
                    storedLayout = existing;
                } else {
                    // Assume alternate key constraint, so increment the generation.
                    storedLayout.setGeneration(nextGeneration(mRepository, storedLayout.getStorableTypeName()));
                    storedLayout.insert();
                }
            }

            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);
                    if (!existing.tryLoad()) {
                        throw e;
                    }
                    storedProperty.copyVersionProperty(existing);
                    if (!existing.equalProperties(storedProperty)) {
                        throw e;
                    }
                }
            }

            txn.commit();

            return new Layout(this, storedLayout);
        } finally {
            txn.exit();
        }
    }

    synchronized void registerReconstructed(Class<? extends Storable> reconstructed, Layout layout) {
        if (mReconstructed == null) {
            mReconstructed = SoftValuedCache.newCache(7);
        }
        mReconstructed.put(reconstructed, layout);
    }

    static int nextGeneration(Repository repo, String typeName) throws FetchException {
        int highestGen = -1;
        {
            Cursor<StoredLayout> cursor;
            try {
                cursor = repo.storageFor(StoredLayout.class).query("storableTypeName = ?").with(typeName)
                        .orderBy("-generation").fetchSlice(0, 1L);
            } catch (RepositoryException e) {
                throw e.toFetchException();
            }

            try {
                if (cursor.hasNext()) {
                    highestGen = cursor.next().getGeneration();
                }
            } finally {
                cursor.close();
            }
        }

        Storage<StoredLayoutEquivalence> es;
        try {
            es = repo.storageFor(StoredLayoutEquivalence.class);
        } catch (RepositoryException e) {
            throw e.toFetchException();
        }

        Cursor<StoredLayoutEquivalence> cursor = es.query("storableTypeName = ?").with(typeName)
                .orderBy("-generation").fetchSlice(0, 1L);

        try {
            if (cursor.hasNext()) {
                highestGen = Math.max(highestGen, cursor.next().getGeneration());
            }
        } finally {
            cursor.close();
        }

        cursor = es.query("storableTypeName = ?").with(typeName).orderBy("-matchedGeneration").fetchSlice(0, 1L);

        try {
            if (cursor.hasNext()) {
                highestGen = Math.max(highestGen, cursor.next().getMatchedGeneration());
            }
        } finally {
            cursor.close();
        }

        return highestGen + 1;
    }

    /**
     * Creates a long hash code that attempts to mix in all relevant layout
     * elements.
     */
    private long mixInHash(long hash, StorableInfo<?> info, LayoutOptions options, int multiplier) {
        hash = mixInHash(hash, info.getStorableType().getName(), multiplier);
        hash = mixInHash(hash, options, 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 relevant 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) {
                hash = hash * multiplier + annHashCode(ann);
            }
        }

        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;
    }

    private long mixInHash(long hash, LayoutOptions options, int multiplier) {
        if (options != null) {
            byte[] data = options.encode();
            if (data != null) {
                for (int b : data) {
                    hash = hash * multiplier + (b & 0xff);
                }
            }
        }
        return hash;
    }

    /**
     * Returns an annotation hash code using a algorithm similar to the
     * default. The difference is in the handling of class and enum values. The
     * name is chosen for the hash code component instead of the instance
     * because it is stable between invocations of the JVM.
     */
    private static int annHashCode(Annotation ann) {
        int hash = 0;

        Method[] methods = ann.getClass().getDeclaredMethods();
        for (Method m : methods) {
            if (m.getReturnType() == null || m.getReturnType() == void.class) {
                continue;
            }
            if (m.getParameterTypes().length != 0) {
                continue;
            }

            String name = m.getName();
            if (name.equals("hashCode") || name.equals("toString") || name.equals("annotationType")) {
                continue;
            }

            Object value;
            try {
                value = m.invoke(ann);
            } catch (InvocationTargetException e) {
                continue;
            } catch (IllegalAccessException e) {
                continue;
            }

            hash += (127 * name.hashCode()) ^ annValueHashCode(value);
        }

        return hash;
    }

    private static int annValueHashCode(Object value) {
        Class type = value.getClass();
        if (!type.isArray()) {
            if (value instanceof String || type.isPrimitive()) {
                return value.hashCode();
            } else if (value instanceof Class) {
                // Use name for stable hash code.
                return ((Class) value).getName().hashCode();
            } else if (value instanceof Enum) {
                // Use name for stable hash code.
                return ((Enum) value).name().hashCode();
            } else if (value instanceof Annotation) {
                return annHashCode((Annotation) value);
            } else {
                return value.hashCode();
            }
        } else if (type == byte[].class) {
            return Arrays.hashCode((byte[]) value);
        } else if (type == char[].class) {
            return Arrays.hashCode((char[]) value);
        } else if (type == double[].class) {
            return Arrays.hashCode((double[]) value);
        } else if (type == float[].class) {
            return Arrays.hashCode((float[]) value);
        } else if (type == int[].class) {
            return Arrays.hashCode((int[]) value);
        } else if (type == long[].class) {
            return Arrays.hashCode((long[]) value);
        } else if (type == short[].class) {
            return Arrays.hashCode((short[]) value);
        } else if (type == boolean[].class) {
            return Arrays.hashCode((boolean[]) value);
        } else {
            return Arrays.hashCode((Object[]) value);
        }
    }
}