/* * Copyright 2007-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.repo.indexed; import java.util.ArrayList; import java.util.List; import com.amazon.carbonado.Cursor; import com.amazon.carbonado.FetchException; import com.amazon.carbonado.PersistException; import com.amazon.carbonado.RepositoryException; import com.amazon.carbonado.Storable; import com.amazon.carbonado.Transaction; import com.amazon.carbonado.Trigger; import com.amazon.carbonado.info.ChainedProperty; /** * Handles index updates for derived-to properties. * * @author Brian S O'Neill * @since 1.2 */ class DerivedIndexesTrigger<S extends Storable, D extends Storable> extends Trigger<S> { private final DependentStorableFetcher<S, D> mFetcher; /** * @param derivedTo special chained property from StorableProperty.getDerivedToProperties */ DerivedIndexesTrigger(IndexedRepository repository, Class<S> sType, ChainedProperty<D> derivedTo) throws RepositoryException { this(new DependentStorableFetcher(repository, sType, derivedTo)); } private DerivedIndexesTrigger(DependentStorableFetcher<S, D> fetcher) { mFetcher = fetcher; } @Override public Object beforeInsert(S storable) throws PersistException { return createDependentIndexEntries(storable); } @Override public void afterInsert(S storable, Object state) throws PersistException { updateValues(storable, state); } @Override public Object beforeUpdate(S storable) throws PersistException { return createDependentIndexEntries(storable); } @Override public void afterUpdate(S storable, Object state) throws PersistException { updateValues(storable, state); } @Override public Object beforeDelete(S storable) throws PersistException { try { if (storable.copy().tryLoad()) { return createDependentIndexEntries(storable); } } catch (FetchException e) { throw e.toPersistException(); } return null; } @Override public void afterDelete(S storable, Object state) throws PersistException { updateValues(storable, state); } @Override public int hashCode() { return mFetcher.hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof DerivedIndexesTrigger) { DerivedIndexesTrigger other = (DerivedIndexesTrigger) obj; return mFetcher.equals(other.mFetcher); } return false; } private List<Storable> createDependentIndexEntries(S storable) throws PersistException { List<Storable> dependentIndexEntries = new ArrayList<Storable>(); createDependentIndexEntries(storable, dependentIndexEntries); return dependentIndexEntries; } private void createDependentIndexEntries(S storable, List<Storable> dependentIndexEntries) throws PersistException { try { Transaction txn = mFetcher.enterTransaction(); try { // Make sure write lock is acquired when reading dependencies // since they might be updated later. Locks are held after this // transaction exits since it is nested in the trigger's transaction. txn.setForUpdate(true); Cursor<D> dependencies = mFetcher.fetchDependenentStorables(storable); try { while (dependencies.hasNext()) { mFetcher.createIndexEntries(dependencies.next(), dependentIndexEntries); } } finally { dependencies.close(); } } finally { txn.exit(); } } catch (FetchException e) { throw e.toPersistException(); } } private void updateValues(S storable, Object state) throws PersistException { if (state == null) { return; } List<Storable> oldIndexEntries = (List<Storable>) state; int size = oldIndexEntries.size(); List<Storable> newIndexEntries = new ArrayList<Storable>(size); createDependentIndexEntries(storable, newIndexEntries); if (size != newIndexEntries.size()) { // This is not expected to happen. throw new PersistException("Amount of affected dependent indexes changed: " + size + " != " + newIndexEntries.size()); } for (int i=0; i<size; i++) { Storable oldIndexEntry = oldIndexEntries.get(i); Storable newIndexEntry = newIndexEntries.get(i); if (!oldIndexEntry.equalProperties(newIndexEntry)) { // Try delete old entry, just in case it is missing. oldIndexEntry.tryDelete(); } // Always try to insert index entry, just in case it is missing. newIndexEntry.tryInsert(); } } /** * Ensure old storable instance is loaded with an upgradable lock, allowing change to * proceed without deadlock. */ final static class Strict<S extends Storable, D extends Storable> extends DerivedIndexesTrigger<S, D> { Strict(IndexedRepository repository, Class<S> sType, ChainedProperty<D> derivedTo) throws RepositoryException { super(repository, sType, derivedTo); } @Override public Object beforeTryDelete(Transaction txn, S storable) throws PersistException { return beforeDelete(txn, storable); } @Override public Object beforeDelete(Transaction txn, S storable) throws PersistException { txn.setForUpdate(true); return super.beforeDelete(storable); } } }