/* * Copyright 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 org.apache.commons.logging.LogFactory; import com.amazon.carbonado.Cursor; import com.amazon.carbonado.Repository; import com.amazon.carbonado.RepositoryException; import com.amazon.carbonado.Storage; import com.amazon.carbonado.Transaction; import com.amazon.carbonado.cursor.FetchAheadCursor; /** * Synchronizes layout metadata between two repositories. Both source and * destination might be updated. * * @author Brian S O'Neill */ public class LayoutSync { private final Repository mSource; private final Repository mDestination; public LayoutSync(Repository source, Repository destination) { mSource = source; mDestination = destination; } /** * @return true if any changes to source were made */ public boolean run() throws RepositoryException { if (doRun()) { // Second pass. doRun(); return true; } return false; } /** * @return true if a second pass should be run */ private boolean doRun() throws RepositoryException { Storage srcLayoutStorage = mSource.storageFor(StoredLayout.class); Storage dstLayoutStorage = mDestination.storageFor(StoredLayout.class); Cursor srcLayouts = srcLayoutStorage.query().orderBy("layoutID").fetch(); try { Cursor dstLayouts = dstLayoutStorage.query().orderBy("layoutID").fetch(); try { return doRun(srcLayouts, dstLayouts); } finally { dstLayouts.close(); } } finally { srcLayouts.close(); } } /** * @return true if another pass should be run */ private boolean doRun(Cursor srcLayouts, Cursor dstLayouts) throws RepositoryException { // Fetch ahead to prevent BDB cursor deadlocks. srcLayouts = new FetchAheadCursor(srcLayouts, 1000); dstLayouts = new FetchAheadCursor(dstLayouts, 1000); boolean doAgain = false; StoredLayout src = null; StoredLayout dst = null; while (true) { if (src == null) { if (!srcLayouts.hasNext()) { return doAgain; } src = srcLayouts.next(); } if (dst == null && dstLayouts.hasNext()) { dst = dstLayouts.next(); } if (dst == null || src.getLayoutID() < dst.getLayoutID()) { // Insert missing layout. If a generation conflict, do over in // second pass after all source generations have been inserted. doAgain |= !tryInsert(src); src = null; continue; } if (src.getLayoutID() > dst.getLayoutID()) { // Skip layout which only exists in destination. dst = null; continue; } if (src.getGeneration() != dst.getGeneration()) { LogFactory.getLog(LayoutSync.class).error ("Unable to synchronize layouts: " + src + " != " + dst); throw new RepositoryException("Unable to synchronize layouts: " + src + " != " + dst); // Same layouts, but with different generation. Create a new // non-conflicting generation to replace both. /* createNewGen(src, dst); doAgain = true; */ } src = null; dst = null; } } /** * @return false if generation conflict */ private boolean tryInsert(StoredLayout src) throws RepositoryException { Storage dstLayoutStorage = mDestination.storageFor(StoredLayout.class); Storage srcPropStorage = mSource.storageFor(StoredLayoutProperty.class); Storage dstPropStorage = mDestination.storageFor(StoredLayoutProperty.class); Transaction txn = mDestination.enterTransaction(); try { txn.setForUpdate(true); StoredLayout dst = dstLayoutStorage.prepare(); src.copyAllProperties(dst); if (!dst.tryInsert()) { return false; } Cursor srcProps = srcPropStorage.query("layoutID = ?").with(src.getLayoutID()).fetch(); while (srcProps.hasNext()) { StoredLayoutProperty srcProp = srcProps.next(); StoredLayoutProperty dstProp = dstPropStorage.prepare(); srcProp.copyAllProperties(dstProp); if (!dstProp.tryInsert()) { dstProp.tryDelete(); dstProp.insert(); } } txn.commit(); } finally { txn.exit(); } return true; } /* private void createNewGen(StoredLayout src, StoredLayout dst) throws RepositoryException { long layoutID = src.getLayoutID(); if (layoutID != dst.getLayoutID()) { throw new AssertionError(); } String storableTypeName = src.getStorableTypeName(); if (!storableTypeName.equals(dst.getStorableTypeName())) { // Assume that there's never any hashcode collision. throw new AssertionError(); } Transaction dstTxn = mDestination.enterTransaction(); try { dstTxn.setForUpdate(true); Transaction srcTxn = mSource.enterTransaction(); try { srcTxn.setForUpdate(true); // New layout generation which is not used by src or dst. int newGen = nextGen(storableTypeName); int oldSrcGen = src.getGeneration(); int oldDstGen = dst.getGeneration(); long now = System.currentTimeMillis(); // Remap source layout to new generation. doCreateNewGen(now, mSource, layoutID, oldSrcGen, newGen); // Remap destination layout to new generation. doCreateNewGen(now, mDestination, layoutID, oldDstGen, newGen); // Check if source has a layout generation matching the // conflicting destination layout. It cannot be used anymore. Cursor srcLayouts = findLayouts(mSource, storableTypeName, oldDstGen); while (srcLayouts.hasNext()) { src = srcLayouts.next(); newGen = nextGen(storableTypeName); doCreateNewGen(now, mSource, src.getLayoutID(), oldDstGen, newGen); } // Check if destination has a layout generation matching the // conflicting source layout. It cannot be used anymore. Cursor dstLayouts = findLayouts(mDestination, storableTypeName, oldSrcGen); while (dstLayouts.hasNext()) { dst = dstLayouts.next(); newGen = nextGen(storableTypeName); doCreateNewGen(now, mDestination, dst.getLayoutID(), oldSrcGen, newGen); } srcTxn.commit(); } finally { srcTxn.exit(); } dstTxn.commit(); } finally { dstTxn.exit(); } } */ /* private void doCreateNewGen(long now, Repository repo, long layoutID, int oldGen, int newGen) throws RepositoryException { Storage layoutStorage = repo.storageFor(StoredLayout.class); Storage equivStorage = repo.storageFor(StoredLayoutEquivalence.class); StoredLayout newLayout = layoutStorage.prepare(); newLayout.setLayoutID(layoutID); newLayout.load(); Layout.fillInCreationInfo(newLayout); newLayout.setGeneration(newGen); // Consistent timestamp for all records. newLayout.setCreationTimestamp(now); newLayout.update(); StoredLayoutEquivalence equiv = equivStorage.prepare(); equiv.setStorableTypeName(newLayout.getStorableTypeName()); equiv.setGeneration(oldGen); equiv.setMatchedGeneration(newGen); equiv.insert(); } private int nextGen(String storableTypeName) throws RepositoryException { return Math.max(nextGen(mSource, storableTypeName), nextGen(mDestination, storableTypeName)); } private int nextGen(Repository repo, String storableTypeName) throws RepositoryException { return LayoutFactory.nextGeneration(repo, storableTypeName); } static Cursor findLayouts(Repository repo, String storableTypeName, int generation) throws RepositoryException { return Layout.findLayouts(repo, storableTypeName, generation); } */ }