diff options
Diffstat (limited to 'src/main/java/com')
4 files changed, 278 insertions, 121 deletions
| diff --git a/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedRepository.java b/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedRepository.java index c292d9c..3b215bc 100644 --- a/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedRepository.java +++ b/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedRepository.java @@ -356,9 +356,9 @@ class ReplicatedRepository                                              Object... filterValues)
          throws RepositoryException
      {
 -        ReplicationTrigger<S> trigger;
 +        ReplicationTrigger<S> replicationTrigger;
          if (storageFor(type) instanceof ReplicatedStorage) {
 -            trigger = ((ReplicatedStorage) storageFor(type)).getTrigger();
 +            replicationTrigger = ((ReplicatedStorage) storageFor(type)).getReplicationTrigger();
          } else {
              throw new UnsupportedTypeException("Storable type is not replicated", type);
          }
 @@ -413,7 +413,7 @@ class ReplicatedRepository          try {
              replicaTxn.setForUpdate(true);
 -            resync(trigger,
 +            resync(replicationTrigger,
                     replicaStorage, replicaQuery,
                     masterStorage, masterQuery,
                     throttle, desiredSpeed,
 @@ -426,7 +426,7 @@ class ReplicatedRepository      }
      @SuppressWarnings("unchecked")
 -    private <S extends Storable> void resync(ReplicationTrigger<S> trigger,
 +    private <S extends Storable> void resync(ReplicationTrigger<S> replicationTrigger,
                                               Storage<S> replicaStorage, Query<S> replicaQuery,
                                               Storage<S> masterStorage, Query<S> masterQuery,
                                               Throttle throttle, double desiredSpeed,
 @@ -493,7 +493,7 @@ class ReplicatedRepository                              if (replicaWithKeyOnly != null) {
                                  // Delete corrupt replica entry.
                                  try {
 -                                    trigger.deleteReplica(replicaWithKeyOnly);
 +                                    replicationTrigger.deleteReplica(replicaWithKeyOnly);
                                      log.info("Deleted corrupt replica entry: " +
                                               replicaWithKeyOnly.toStringKeyOnly(), e);
                                      skip = false;
 @@ -551,7 +551,7 @@ class ReplicatedRepository                  if (compare < 0) {
                      // Bogus record exists only in replica so delete it.
 -                    resyncTask = prepareResyncTask(trigger, replicaEntry, null);
 +                    resyncTask = prepareResyncTask(replicationTrigger, replicaEntry, null);
                      // Allow replica to advance.
                      if (replicaCursor == null) {
                          replicaCursor = replicaQuery.fetchAfter(replicaEntry);
 @@ -560,7 +560,7 @@ class ReplicatedRepository                      replicaEntry = null;
                  } else if (compare > 0) {
                      // Replica cursor is missing an entry so copy it.
 -                    resyncTask = prepareResyncTask(trigger, null, masterEntry);
 +                    resyncTask = prepareResyncTask(replicationTrigger, null, masterEntry);
                      // Allow master to advance.
                      lastMasterEntry = masterEntry;
                      masterEntry = null;
 @@ -576,7 +576,8 @@ class ReplicatedRepository                      // Both replicaEntry and masterEntry are non-null.
                      if (!replicaEntry.equalProperties(masterEntry)) {
                          // Replica is stale.
 -                        resyncTask = prepareResyncTask(trigger, replicaEntry, masterEntry);
 +                        resyncTask = prepareResyncTask
 +                            (replicationTrigger, replicaEntry, masterEntry);
                      }
                      // Entries are synchronized so allow both cursors to advance.
 @@ -607,9 +608,10 @@ class ReplicatedRepository          }
      }
 -    private <S extends Storable> Runnable prepareResyncTask(final ReplicationTrigger<S> trigger,
 -                                                            final S replicaEntry,
 -                                                            final S masterEntry)
 +    private <S extends Storable> Runnable prepareResyncTask
 +                       (final ReplicationTrigger<S> replicationTrigger,
 +                        final S replicaEntry,
 +                        final S masterEntry)
          throws RepositoryException
      {
          if (replicaEntry == null && masterEntry == null) {
 @@ -622,7 +624,7 @@ class ReplicatedRepository          Runnable task = new Runnable() {
              public void run() {
                  try {
 -                    trigger.resyncEntries(replicaEntry, masterEntry);
 +                    replicationTrigger.resyncEntries(replicaEntry, masterEntry);
                  } catch (Exception e) {
                      LogFactory.getLog(ReplicatedRepository.class).error(null, e);
                  }
 diff --git a/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedStorage.java b/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedStorage.java index ebb6db6..9711784 100644 --- a/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedStorage.java +++ b/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedStorage.java @@ -43,7 +43,7 @@ import com.amazon.carbonado.spi.BelatedStorageCreator;  class ReplicatedStorage<S extends Storable> implements Storage<S> {
      final Storage<S> mReplicaStorage;
      final Storage<S> mMasterStorage;
 -    final ReplicationTrigger<S> mTrigger;
 +    final ReplicationTrigger<S> mReplicationTrigger;
      /**
       * @throws UnsupportedTypeException if master doesn't support Storable, but
 @@ -63,8 +63,13 @@ class ReplicatedStorage<S extends Storable> implements Storage<S> {               ReplicatedRepositoryBuilder.DEFAULT_RETRY_MILLIS);
          mMasterStorage = creator.get(ReplicatedRepositoryBuilder.DEFAULT_MASTER_TIMEOUT_MILLIS);
 -        mTrigger = new ReplicationTrigger<S>(aRepository, mReplicaStorage, mMasterStorage);
 -        mReplicaStorage.addTrigger(mTrigger);
 +
 +        // ReplicationTrigger contains internal TriggerManager, and all other
 +        // triggers should register with the ReplicationTrigger. This allows
 +        // all triggers to be easily disabled during resync and repairs.
 +
 +        mReplicationTrigger = new ReplicationTrigger<S>
 +            (aRepository, mReplicaStorage, mMasterStorage);
      }
      /**
 @@ -76,8 +81,8 @@ class ReplicatedStorage<S extends Storable> implements Storage<S> {      {
          mReplicaStorage = replicaStorage;
          mMasterStorage = masterStorage;
 -        mTrigger = new ReplicationTrigger<S>(aRepository, mReplicaStorage, masterStorage);
 -        mReplicaStorage.addTrigger(mTrigger);
 +        mReplicationTrigger = new ReplicationTrigger<S>
 +            (aRepository, mReplicaStorage, masterStorage);
      }
      public Class<S> getStorableType() {
 @@ -100,21 +105,15 @@ class ReplicatedStorage<S extends Storable> implements Storage<S> {          return mReplicaStorage.query(filter);
      }
 -    // Note: All user triggers must be added to the master storage. Otherwise,
 -    // resync operations can cause the triggers to run again, which can be
 -    // disastrous. If triggers ever support "after load" events, things get
 -    // complicated. Perhaps this use case is a good example for why supporting
 -    // "after load" events might be bad.
 -
      public boolean addTrigger(Trigger<? super S> trigger) {
 -        return mMasterStorage.addTrigger(trigger);
 +        return mReplicationTrigger.addTrigger(trigger);
      }
      public boolean removeTrigger(Trigger<? super S> trigger) {
 -        return mMasterStorage.removeTrigger(trigger);
 +        return mReplicationTrigger.removeTrigger(trigger);
      }
 -    ReplicationTrigger<S> getTrigger() {
 -        return mTrigger;
 +    ReplicationTrigger<S> getReplicationTrigger() {
 +        return mReplicationTrigger;
      }
  }
 diff --git a/src/main/java/com/amazon/carbonado/repo/replicated/ReplicationTrigger.java b/src/main/java/com/amazon/carbonado/repo/replicated/ReplicationTrigger.java index 249a779..7f743ac 100644 --- a/src/main/java/com/amazon/carbonado/repo/replicated/ReplicationTrigger.java +++ b/src/main/java/com/amazon/carbonado/repo/replicated/ReplicationTrigger.java @@ -18,8 +18,6 @@  package com.amazon.carbonado.repo.replicated;
 -import java.util.concurrent.atomic.AtomicInteger;
 -
  import org.apache.commons.logging.Log;
  import org.apache.commons.logging.LogFactory;
 @@ -36,6 +34,7 @@ import com.amazon.carbonado.Trigger;  import com.amazon.carbonado.UniqueConstraintException;
  import com.amazon.carbonado.spi.RepairExecutor;
 +import com.amazon.carbonado.spi.TriggerManager;
  /**
   * All inserts/updates/deletes are first committed to the master storage, then
 @@ -49,7 +48,7 @@ class ReplicationTrigger<S extends Storable> extends Trigger<S> {      private final Storage<S> mReplicaStorage;
      private final Storage<S> mMasterStorage;
 -    private final ThreadLocal<AtomicInteger> mDisabled = new ThreadLocal<AtomicInteger>();
 +    private final TriggerManager<S> mTriggerManager;
      ReplicationTrigger(Repository repository,
                         Storage<S> replicaStorage,
 @@ -58,6 +57,11 @@ class ReplicationTrigger<S extends Storable> extends Trigger<S> {          mRepository = repository;
          mReplicaStorage = replicaStorage;
          mMasterStorage = masterStorage;
 +        // Use TriggerManager to locally disable trigger execution during
 +        // resync and repairs.
 +        mTriggerManager = new TriggerManager<S>();
 +        mTriggerManager.addTrigger(this);
 +        replicaStorage.addTrigger(mTriggerManager);
      }
      @Override
 @@ -71,10 +75,6 @@ class ReplicationTrigger<S extends Storable> extends Trigger<S> {      }
      private Object beforeInsert(S replica, boolean forTry) throws PersistException {
 -        if (isReplicationDisabled()) {
 -            return null;
 -        }
 -
          final S master = mMasterStorage.prepare();
          replica.copyAllProperties(master);
 @@ -129,10 +129,6 @@ class ReplicationTrigger<S extends Storable> extends Trigger<S> {      }
      private Object beforeUpdate(S replica, boolean forTry) throws PersistException {
 -        if (isReplicationDisabled()) {
 -            return null;
 -        }
 -
          final S master = mMasterStorage.prepare();
          replica.copyPrimaryKeyProperties(master);
 @@ -209,10 +205,6 @@ class ReplicationTrigger<S extends Storable> extends Trigger<S> {      @Override
      public Object beforeDelete(S replica) throws PersistException {
 -        if (isReplicationDisabled()) {
 -            return null;
 -        }
 -
          S master = mMasterStorage.prepare();
          replica.copyPrimaryKeyProperties(master);
 @@ -373,6 +365,14 @@ class ReplicationTrigger<S extends Storable> extends Trigger<S> {          });
      }
 +    boolean addTrigger(Trigger<? super S> trigger) {
 +        return mTriggerManager.addTrigger(trigger);
 +    }
 +
 +    boolean removeTrigger(Trigger<? super S> trigger) {
 +        return mTriggerManager.removeTrigger(trigger);
 +    }
 +
      /**
       * Deletes the replica entry with replication disabled.
       */
 @@ -401,35 +401,13 @@ class ReplicationTrigger<S extends Storable> extends Trigger<S> {          }
      }
 -    /**
 -     * Returns true if replication is disabled for the current thread.
 -     */
 -    private boolean isReplicationDisabled() {
 -        // Count indicates how many times disabled (nested)
 -        AtomicInteger i = mDisabled.get();
 -        return i != null && i.get() > 0;
 -    }
 -
 -    /**
 -     * By default, replication is enabled for the current thread. Pass true to
 -     * disable during re-sync operations.
 -     */
 -    private void setReplicationDisabled(boolean disabled) {
 -        // Using a count allows this method call to be nested. Based on the
 -        // current implementation, it should never be nested, so this extra
 -        // work is just a safeguard.
 -        AtomicInteger i = mDisabled.get();
 +    void setReplicationDisabled(boolean disabled) {
 +        // This method disables not only this trigger, but all triggers added
 +        // to manager.
          if (disabled) {
 -            if (i == null) {
 -                i = new AtomicInteger(1);
 -                mDisabled.set(i);
 -            } else {
 -                i.incrementAndGet();
 -            }
 +            mTriggerManager.localDisable();
          } else {
 -            if (i != null) {
 -                i.decrementAndGet();
 -            }
 +            mTriggerManager.localEnable();
          }
      }
  }
 diff --git a/src/main/java/com/amazon/carbonado/spi/TriggerManager.java b/src/main/java/com/amazon/carbonado/spi/TriggerManager.java index 147d1ed..a67b134 100644 --- a/src/main/java/com/amazon/carbonado/spi/TriggerManager.java +++ b/src/main/java/com/amazon/carbonado/spi/TriggerManager.java @@ -23,6 +23,9 @@ import java.lang.reflect.Method;  import java.util.ArrayList;
  import java.util.Arrays;
 +import java.util.concurrent.atomic.AtomicInteger;
 +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
 +
  import com.amazon.carbonado.PersistException;
  import com.amazon.carbonado.RepositoryException;
  import com.amazon.carbonado.Storable;
 @@ -36,7 +39,7 @@ import com.amazon.carbonado.TriggerFactory;   *
   * @author Brian S O'Neill
   */
 -public class TriggerManager<S extends Storable> {
 +public class TriggerManager<S extends Storable> extends Trigger<S> {
      // Bit masks returned by selectTypes.
      private static final int FOR_INSERT = 1;
      private static final int FOR_UPDATE = 2;
 @@ -91,9 +94,9 @@ public class TriggerManager<S extends Storable> {          }
      }
 -    private volatile ForInsert<S> mForInsert;
 -    private volatile ForUpdate<S> mForUpdate;
 -    private volatile ForDelete<S> mForDelete;
 +    private final ForInsert<S> mForInsert = new ForInsert<S>();
 +    private final ForUpdate<S> mForUpdate = new ForUpdate<S>();
 +    private final ForDelete<S> mForDelete = new ForDelete<S>();
      public TriggerManager() {
      }
 @@ -111,102 +114,74 @@ public class TriggerManager<S extends Storable> {      }
      /**
 -     * Returns consolidated trigger to call for insert operations, or null if
 -     * none.
 +     * Returns a consolidated trigger to call for insert operations, or null if
 +     * none. If not null, the consolidated trigger is not a snapshot -- it will
 +     * change as the set of triggers in this manager changes.
       */
      public Trigger<? super S> getInsertTrigger() {
 -        return mForInsert;
 +        ForInsert<S> forInsert = mForInsert;
 +        return forInsert.isEmpty() ? null : forInsert;
      }
      /**
 -     * Returns consolidated trigger to call for update operations, or null if
 -     * none.
 +     * Returns a consolidated trigger to call for update operations, or null if
 +     * none. If not null, the consolidated trigger is not a snapshot -- it will
 +     * change as the set of triggers in this manager changes.
       */
      public Trigger<? super S> getUpdateTrigger() {
 -        return mForUpdate;
 +        ForUpdate<S> forUpdate = mForUpdate;
 +        return forUpdate.isEmpty() ? null : forUpdate;
      }
      /**
 -     * Returns consolidated trigger to call for delete operations, or null if
 -     * none.
 +     * Returns a consolidated trigger to call for delete operations, or null if
 +     * none. If not null, the consolidated trigger is not a snapshot -- it will
 +     * change as the set of triggers in this manager changes.
       */
      public Trigger<? super S> getDeleteTrigger() {
 -        return mForDelete;
 +        ForDelete<S> forDelete = mForDelete;
 +        return forDelete.isEmpty() ? null : forDelete;
      }
 -    public synchronized boolean addTrigger(Trigger<? super S> trigger) {
 +    public boolean addTrigger(Trigger<? super S> trigger) {
          if (trigger == null) {
              throw new IllegalArgumentException();
          }
          int types = selectTypes(trigger);
 -        if (types == 0) {
 -            return false;
 -        }
          boolean retValue = false;
          if ((types & FOR_INSERT) != 0) {
 -            if (mForInsert == null) {
 -                mForInsert = new ForInsert<S>();
 -            }
              retValue |= mForInsert.add(trigger);
          }
 -
          if ((types & FOR_UPDATE) != 0) {
 -            if (mForUpdate == null) {
 -                mForUpdate = new ForUpdate<S>();
 -            }
              retValue |= mForUpdate.add(trigger);
          }
 -
          if ((types & FOR_DELETE) != 0) {
 -            if (mForDelete == null) {
 -                mForDelete = new ForDelete<S>();
 -            }
              retValue |= mForDelete.add(trigger);
          }
          return retValue;
      }
 -    public synchronized boolean removeTrigger(Trigger<? super S> trigger) {
 +    public boolean removeTrigger(Trigger<? super S> trigger) {
          if (trigger == null) {
              throw new IllegalArgumentException();
          }
          int types = selectTypes(trigger);
 -        if (types == 0) {
 -            return false;
 -        }
          boolean retValue = false;
          if ((types & FOR_INSERT) != 0) {
 -            if (mForInsert != null && mForInsert.remove(trigger)) {
 -                retValue = true;
 -                if (mForInsert.isEmpty()) {
 -                    mForInsert = null;
 -                }
 -            }
 +            retValue |= mForInsert.remove(trigger);
          }
 -
          if ((types & FOR_UPDATE) != 0) {
 -            if (mForUpdate != null && mForUpdate.remove(trigger)) {
 -                retValue = true;
 -                if (mForUpdate.isEmpty()) {
 -                    mForUpdate = null;
 -                }
 -            }
 +            retValue |= mForUpdate.remove(trigger);
          }
 -
          if ((types & FOR_DELETE) != 0) {
 -            if (mForDelete != null && mForDelete.remove(trigger)) {
 -                retValue = true;
 -                if (mForDelete.isEmpty()) {
 -                    mForDelete = null;
 -                }
 -            }
 +            retValue |= mForDelete.remove(trigger);
          }
          return retValue;
 @@ -224,6 +199,102 @@ public class TriggerManager<S extends Storable> {      }
      /**
 +     * Disables execution of all managed triggers for the current thread. Call
 +     * localEnable to enable again. This call can be made multiple times, but
 +     * be sure to call localEnable the same number of times to fully enable.
 +     */
 +    public void localDisable() {
 +        mForInsert.localDisable();
 +        mForUpdate.localDisable();
 +        mForDelete.localDisable();
 +    }
 +
 +    /**
 +     * Enables execution of all managed triggers for the current thread, if
 +     * they had been disabled before.
 +     */
 +    public void localEnable() {
 +        mForInsert.localEnable();
 +        mForUpdate.localEnable();
 +        mForDelete.localEnable();
 +    }
 +
 +    @Override
 +    public Object beforeInsert(S storable) throws PersistException {
 +        return mForInsert.beforeInsert(storable);
 +    }
 +
 +    @Override
 +    public Object beforeTryInsert(S storable) throws PersistException {
 +        return mForInsert.beforeTryInsert(storable);
 +    }
 +
 +    @Override
 +    public void afterInsert(S storable, Object state) throws PersistException {
 +        mForInsert.afterInsert(storable, state);
 +    }
 +
 +    @Override
 +    public void afterTryInsert(S storable, Object state) throws PersistException {
 +        mForInsert.afterTryInsert(storable, state);
 +    }
 +
 +    @Override
 +    public void failedInsert(S storable, Object state) {
 +        mForInsert.failedInsert(storable, state);
 +    }
 +
 +    @Override
 +    public Object beforeUpdate(S storable) throws PersistException {
 +        return mForUpdate.beforeUpdate(storable);
 +    }
 +
 +    @Override
 +    public Object beforeTryUpdate(S storable) throws PersistException {
 +        return mForUpdate.beforeTryUpdate(storable);
 +    }
 +
 +    @Override
 +    public void afterUpdate(S storable, Object state) throws PersistException {
 +        mForUpdate.afterUpdate(storable, state);
 +    }
 +
 +    @Override
 +    public void afterTryUpdate(S storable, Object state) throws PersistException {
 +        mForUpdate.afterTryUpdate(storable, state);
 +    }
 +
 +    @Override
 +    public void failedUpdate(S storable, Object state) {
 +        mForUpdate.failedUpdate(storable, state);
 +    }
 +
 +    @Override
 +    public Object beforeDelete(S storable) throws PersistException {
 +        return mForDelete.beforeDelete(storable);
 +    }
 +
 +    @Override
 +    public Object beforeTryDelete(S storable) throws PersistException {
 +        return mForDelete.beforeTryDelete(storable);
 +    }
 +
 +    @Override
 +    public void afterDelete(S storable, Object state) throws PersistException {
 +        mForDelete.afterDelete(storable, state);
 +    }
 +
 +    @Override
 +    public void afterTryDelete(S storable, Object state) throws PersistException {
 +        mForDelete.afterTryDelete(storable, state);
 +    }
 +
 +    @Override
 +    public void failedDelete(S storable, Object state) {
 +        mForDelete.failedDelete(storable, state);
 +    }
 +
 +    /**
       * Determines which operations the given trigger overrides.
       */
      private int selectTypes(Trigger<? super S> trigger) {
 @@ -281,10 +352,16 @@ public class TriggerManager<S extends Storable> {      }
      private static abstract class ForSomething<S> extends Trigger<S> {
 +        private static final AtomicReferenceFieldUpdater<ForSomething, ThreadLocal>
 +            cDisabledFlagRef = AtomicReferenceFieldUpdater
 +             .newUpdater(ForSomething.class, ThreadLocal.class, "mDisabledFlag");
 +
          private static Trigger[] NO_TRIGGERS = new Trigger[0];
          protected volatile Trigger<? super S>[] mTriggers;
 +        private volatile ThreadLocal<AtomicInteger> mDisabledFlag;
 +
          ForSomething() {
              mTriggers = NO_TRIGGERS;
          }
 @@ -313,11 +390,56 @@ public class TriggerManager<S extends Storable> {          boolean isEmpty() {
              return mTriggers.length == 0;
          }
 +
 +        boolean isLocallyDisabled() {
 +            ThreadLocal<AtomicInteger> disabledFlag = mDisabledFlag;
 +            if (disabledFlag == null) {
 +                return false;
 +            }
 +            // Count indicates how many times disabled (nested)
 +            AtomicInteger i = disabledFlag.get();
 +            return i != null && i.get() > 0;
 +        }
 +
 +        void localDisable() {
 +            // Using a count allows this method call to be nested.
 +            ThreadLocal<AtomicInteger> disabledFlag = disabledFlag();
 +            AtomicInteger i = disabledFlag.get();
 +            if (i == null) {
 +                disabledFlag.set(new AtomicInteger(1));
 +            } else {
 +                i.incrementAndGet();
 +            }
 +        }
 +
 +        void localEnable() {
 +            // Using a count allows this method call to be nested.
 +            AtomicInteger i = disabledFlag().get();
 +            if (i != null) {
 +                i.decrementAndGet();
 +            }
 +        }
 +
 +        private ThreadLocal<AtomicInteger> disabledFlag() {
 +            ThreadLocal<AtomicInteger> disabledFlag = mDisabledFlag;
 +            while (disabledFlag == null) {
 +                disabledFlag = new ThreadLocal<AtomicInteger>();
 +                if (cDisabledFlagRef.compareAndSet(this, null, disabledFlag)) {
 +                    break;
 +                }
 +                disabledFlag = mDisabledFlag;
 +            }
 +            return disabledFlag;
 +        }
      }
      private static class ForInsert<S> extends ForSomething<S> {
          @Override
          public Object beforeInsert(S storable) throws PersistException {
 +            if (isLocallyDisabled()) {
 +                return null;
 +            }
 +
              TriggerStates<S> triggerStates = null;
              Trigger<? super S>[] triggers = mTriggers;
 @@ -336,6 +458,10 @@ public class TriggerManager<S extends Storable> {          @Override
          public Object beforeTryInsert(S storable) throws PersistException {
 +            if (isLocallyDisabled()) {
 +                return null;
 +            }
 +
              TriggerStates<S> triggerStates = null;
              Trigger<? super S>[] triggers = mTriggers;
 @@ -354,6 +480,10 @@ public class TriggerManager<S extends Storable> {          @Override
          public void afterInsert(S storable, Object state) throws PersistException {
 +            if (isLocallyDisabled()) {
 +                return;
 +            }
 +
              TriggerStates<S> triggerStates;
              Trigger<? super S>[] triggers;
 @@ -383,6 +513,10 @@ public class TriggerManager<S extends Storable> {          @Override
          public void afterTryInsert(S storable, Object state) throws PersistException {
 +            if (isLocallyDisabled()) {
 +                return;
 +            }
 +
              TriggerStates<S> triggerStates;
              Trigger<? super S>[] triggers;
 @@ -412,6 +546,10 @@ public class TriggerManager<S extends Storable> {          @Override
          public void failedInsert(S storable, Object state) {
 +            if (isLocallyDisabled()) {
 +                return;
 +            }
 +
              TriggerStates<S> triggerStates;
              Trigger<? super S>[] triggers;
 @@ -453,6 +591,10 @@ public class TriggerManager<S extends Storable> {      private static class ForUpdate<S> extends ForSomething<S> {
          @Override
          public Object beforeUpdate(S storable) throws PersistException {
 +            if (isLocallyDisabled()) {
 +                return null;
 +            }
 +
              TriggerStates<S> triggerStates = null;
              Trigger<? super S>[] triggers = mTriggers;
 @@ -471,6 +613,10 @@ public class TriggerManager<S extends Storable> {          @Override
          public Object beforeTryUpdate(S storable) throws PersistException {
 +            if (isLocallyDisabled()) {
 +                return null;
 +            }
 +
              TriggerStates<S> triggerStates = null;
              Trigger<? super S>[] triggers = mTriggers;
 @@ -489,6 +635,10 @@ public class TriggerManager<S extends Storable> {          @Override
          public void afterUpdate(S storable, Object state) throws PersistException {
 +            if (isLocallyDisabled()) {
 +                return;
 +            }
 +
              TriggerStates<S> triggerStates;
              Trigger<? super S>[] triggers;
 @@ -518,6 +668,10 @@ public class TriggerManager<S extends Storable> {          @Override
          public void afterTryUpdate(S storable, Object state) throws PersistException {
 +            if (isLocallyDisabled()) {
 +                return;
 +            }
 +
              TriggerStates<S> triggerStates;
              Trigger<? super S>[] triggers;
 @@ -547,6 +701,10 @@ public class TriggerManager<S extends Storable> {          @Override
          public void failedUpdate(S storable, Object state) {
 +            if (isLocallyDisabled()) {
 +                return;
 +            }
 +
              TriggerStates<S> triggerStates;
              Trigger<? super S>[] triggers;
 @@ -588,6 +746,10 @@ public class TriggerManager<S extends Storable> {      private static class ForDelete<S> extends ForSomething<S> {
          @Override
          public Object beforeDelete(S storable) throws PersistException {
 +            if (isLocallyDisabled()) {
 +                return null;
 +            }
 +
              TriggerStates<S> triggerStates = null;
              Trigger<? super S>[] triggers = mTriggers;
 @@ -606,6 +768,10 @@ public class TriggerManager<S extends Storable> {          @Override
          public Object beforeTryDelete(S storable) throws PersistException {
 +            if (isLocallyDisabled()) {
 +                return null;
 +            }
 +
              TriggerStates<S> triggerStates = null;
              Trigger<? super S>[] triggers = mTriggers;
 @@ -624,6 +790,10 @@ public class TriggerManager<S extends Storable> {          @Override
          public void afterDelete(S storable, Object state) throws PersistException {
 +            if (isLocallyDisabled()) {
 +                return;
 +            }
 +
              TriggerStates<S> triggerStates;
              Trigger<? super S>[] triggers;
 @@ -653,6 +823,10 @@ public class TriggerManager<S extends Storable> {          @Override
          public void afterTryDelete(S storable, Object state) throws PersistException {
 +            if (isLocallyDisabled()) {
 +                return;
 +            }
 +
              TriggerStates<S> triggerStates;
              Trigger<? super S>[] triggers;
 @@ -682,6 +856,10 @@ public class TriggerManager<S extends Storable> {          @Override
          public void failedDelete(S storable, Object state) {
 +            if (isLocallyDisabled()) {
 +                return;
 +            }
 +
              TriggerStates<S> triggerStates;
              Trigger<? super S>[] triggers;
 | 
