예제 #1
0
  @Override
  public <L extends AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>>
      ListenerRegistration<L> registerChangeListener(
          final YangInstanceIdentifier path, final L listener, final DataChangeScope scope) {

    /*
     * Make sure commit is not occurring right now. Listener has to be
     * registered and its state capture enqueued at a consistent point.
     *
     * FIXME: improve this to read-write lock, such that multiple listener
     * registrations can occur simultaneously
     */
    final DataChangeListenerRegistration<L> reg;
    synchronized (this) {
      LOG.debug("{}: Registering data change listener {} for {}", name, listener, path);

      reg = listenerTree.registerDataChangeListener(path, listener, scope);

      Optional<NormalizedNode<?, ?>> currentState = dataTree.takeSnapshot().readNode(path);
      if (currentState.isPresent()) {
        final NormalizedNode<?, ?> data = currentState.get();

        final DOMImmutableDataChangeEvent event =
            DOMImmutableDataChangeEvent.builder(DataChangeScope.BASE) //
                .setAfter(data) //
                .addCreated(path, data) //
                .build();
        executor.submit(new ChangeListenerNotifyTask(Collections.singletonList(reg), event));
      }
    }

    return new AbstractListenerRegistration<L>(listener) {
      @Override
      protected void removeRegistration() {
        synchronized (InMemoryDOMDataStore.this) {
          reg.close();
        }
      }
    };
  }
예제 #2
0
/**
 * In-memory DOM Data Store
 *
 * <p>Implementation of {@link DOMStore} which uses {@link DataTree} and other classes such as
 * {@link SnapshotBackedWriteTransaction}. {@link SnapshotBackedReadTransaction} and {@link
 * ResolveDataChangeEventsTask} to implement {@link DOMStore} contract.
 */
public class InMemoryDOMDataStore
    implements DOMStore,
        Identifiable<String>,
        SchemaContextListener,
        TransactionReadyPrototype,
        AutoCloseable {
  private static final Logger LOG = LoggerFactory.getLogger(InMemoryDOMDataStore.class);
  private final DataTree dataTree = InMemoryDataTreeFactory.getInstance().create();
  private final ListenerTree listenerTree = ListenerTree.create();
  private final AtomicLong txCounter = new AtomicLong(0);
  private final ListeningExecutorService executor;

  private final String name;

  public InMemoryDOMDataStore(final String name, final ListeningExecutorService executor) {
    this.name = Preconditions.checkNotNull(name);
    this.executor = Preconditions.checkNotNull(executor);
  }

  @Override
  public final String getIdentifier() {
    return name;
  }

  @Override
  public DOMStoreReadTransaction newReadOnlyTransaction() {
    return new SnapshotBackedReadTransaction(nextIdentifier(), dataTree.takeSnapshot());
  }

  @Override
  public DOMStoreReadWriteTransaction newReadWriteTransaction() {
    return new SnapshotBackedReadWriteTransaction(nextIdentifier(), dataTree.takeSnapshot(), this);
  }

  @Override
  public DOMStoreWriteTransaction newWriteOnlyTransaction() {
    return new SnapshotBackedWriteTransaction(nextIdentifier(), dataTree.takeSnapshot(), this);
  }

  @Override
  public DOMStoreTransactionChain createTransactionChain() {
    return new DOMStoreTransactionChainImpl();
  }

  @Override
  public synchronized void onGlobalContextUpdated(final SchemaContext ctx) {
    dataTree.setSchemaContext(ctx);
  }

  @Override
  public void close() {
    executor.shutdownNow();
  }

  @Override
  public <L extends AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>>
      ListenerRegistration<L> registerChangeListener(
          final YangInstanceIdentifier path, final L listener, final DataChangeScope scope) {

    /*
     * Make sure commit is not occurring right now. Listener has to be
     * registered and its state capture enqueued at a consistent point.
     *
     * FIXME: improve this to read-write lock, such that multiple listener
     * registrations can occur simultaneously
     */
    final DataChangeListenerRegistration<L> reg;
    synchronized (this) {
      LOG.debug("{}: Registering data change listener {} for {}", name, listener, path);

      reg = listenerTree.registerDataChangeListener(path, listener, scope);

      Optional<NormalizedNode<?, ?>> currentState = dataTree.takeSnapshot().readNode(path);
      if (currentState.isPresent()) {
        final NormalizedNode<?, ?> data = currentState.get();

        final DOMImmutableDataChangeEvent event =
            DOMImmutableDataChangeEvent.builder(DataChangeScope.BASE) //
                .setAfter(data) //
                .addCreated(path, data) //
                .build();
        executor.submit(new ChangeListenerNotifyTask(Collections.singletonList(reg), event));
      }
    }

    return new AbstractListenerRegistration<L>(listener) {
      @Override
      protected void removeRegistration() {
        synchronized (InMemoryDOMDataStore.this) {
          reg.close();
        }
      }
    };
  }

  @Override
  public synchronized DOMStoreThreePhaseCommitCohort ready(
      final SnapshotBackedWriteTransaction writeTx) {
    LOG.debug(
        "Tx: {} is submitted. Modifications: {}",
        writeTx.getIdentifier(),
        writeTx.getMutatedView());
    return new ThreePhaseCommitImpl(writeTx);
  }

  private Object nextIdentifier() {
    return name + "-" + txCounter.getAndIncrement();
  }

  private class DOMStoreTransactionChainImpl
      implements DOMStoreTransactionChain, TransactionReadyPrototype {

    @GuardedBy("this")
    private SnapshotBackedWriteTransaction latestOutstandingTx;

    private boolean chainFailed = false;

    private void checkFailed() {
      Preconditions.checkState(!chainFailed, "Transaction chain is failed.");
    }

    @Override
    public synchronized DOMStoreReadTransaction newReadOnlyTransaction() {
      final DataTreeSnapshot snapshot;
      checkFailed();
      if (latestOutstandingTx != null) {
        checkState(latestOutstandingTx.isReady(), "Previous transaction in chain must be ready.");
        snapshot = latestOutstandingTx.getMutatedView();
      } else {
        snapshot = dataTree.takeSnapshot();
      }
      return new SnapshotBackedReadTransaction(nextIdentifier(), snapshot);
    }

    @Override
    public synchronized DOMStoreReadWriteTransaction newReadWriteTransaction() {
      final DataTreeSnapshot snapshot;
      checkFailed();
      if (latestOutstandingTx != null) {
        checkState(latestOutstandingTx.isReady(), "Previous transaction in chain must be ready.");
        snapshot = latestOutstandingTx.getMutatedView();
      } else {
        snapshot = dataTree.takeSnapshot();
      }
      final SnapshotBackedReadWriteTransaction ret =
          new SnapshotBackedReadWriteTransaction(nextIdentifier(), snapshot, this);
      latestOutstandingTx = ret;
      return ret;
    }

    @Override
    public synchronized DOMStoreWriteTransaction newWriteOnlyTransaction() {
      final DataTreeSnapshot snapshot;
      checkFailed();
      if (latestOutstandingTx != null) {
        checkState(latestOutstandingTx.isReady(), "Previous transaction in chain must be ready.");
        snapshot = latestOutstandingTx.getMutatedView();
      } else {
        snapshot = dataTree.takeSnapshot();
      }
      final SnapshotBackedWriteTransaction ret =
          new SnapshotBackedWriteTransaction(nextIdentifier(), snapshot, this);
      latestOutstandingTx = ret;
      return ret;
    }

    @Override
    public DOMStoreThreePhaseCommitCohort ready(final SnapshotBackedWriteTransaction tx) {
      DOMStoreThreePhaseCommitCohort storeCohort = InMemoryDOMDataStore.this.ready(tx);
      return new ChainedTransactionCommitImpl(tx, storeCohort, this);
    }

    @Override
    public void close() {

      executor.shutdownNow();
    }

    protected synchronized void onTransactionFailed(
        final SnapshotBackedWriteTransaction transaction, final Throwable t) {
      chainFailed = true;
    }

    public synchronized void onTransactionCommited(
        final SnapshotBackedWriteTransaction transaction) {
      // If commited transaction is latestOutstandingTx we clear
      // latestOutstandingTx
      // field in order to base new transactions on Datastore Data Tree
      // directly.
      if (transaction.equals(latestOutstandingTx)) {
        latestOutstandingTx = null;
      }
    }
  }

  private static class ChainedTransactionCommitImpl implements DOMStoreThreePhaseCommitCohort {

    private final SnapshotBackedWriteTransaction transaction;
    private final DOMStoreThreePhaseCommitCohort delegate;

    private final DOMStoreTransactionChainImpl txChain;

    protected ChainedTransactionCommitImpl(
        final SnapshotBackedWriteTransaction transaction,
        final DOMStoreThreePhaseCommitCohort delegate,
        final DOMStoreTransactionChainImpl txChain) {
      super();
      this.transaction = transaction;
      this.delegate = delegate;
      this.txChain = txChain;
    }

    @Override
    public ListenableFuture<Boolean> canCommit() {
      return delegate.canCommit();
    }

    @Override
    public ListenableFuture<Void> preCommit() {
      return delegate.preCommit();
    }

    @Override
    public ListenableFuture<Void> abort() {
      return delegate.abort();
    }

    @Override
    public ListenableFuture<Void> commit() {
      ListenableFuture<Void> commitFuture = delegate.commit();
      Futures.addCallback(
          commitFuture,
          new FutureCallback<Void>() {
            @Override
            public void onFailure(final Throwable t) {
              txChain.onTransactionFailed(transaction, t);
            }

            @Override
            public void onSuccess(final Void result) {
              txChain.onTransactionCommited(transaction);
            }
          });
      return commitFuture;
    }
  }

  private class ThreePhaseCommitImpl implements DOMStoreThreePhaseCommitCohort {

    private final SnapshotBackedWriteTransaction transaction;
    private final DataTreeModification modification;

    private ResolveDataChangeEventsTask listenerResolver;
    private DataTreeCandidate candidate;

    public ThreePhaseCommitImpl(final SnapshotBackedWriteTransaction writeTransaction) {
      this.transaction = writeTransaction;
      this.modification = transaction.getMutatedView();
    }

    @Override
    public ListenableFuture<Boolean> canCommit() {
      return executor.submit(
          new Callable<Boolean>() {
            @Override
            public Boolean call() throws TransactionCommitFailedException {
              try {
                dataTree.validate(modification);
                LOG.debug("Store Transaction: {} can be committed", transaction.getIdentifier());
                return true;
              } catch (ConflictingModificationAppliedException e) {
                LOG.warn(
                    "Store Tx: {} Conflicting modification for {}.",
                    transaction.getIdentifier(),
                    e.getPath());
                throw new OptimisticLockFailedException("Optimistic lock failed.", e);
              } catch (DataValidationFailedException e) {
                LOG.warn(
                    "Store Tx: {} Data Precondition failed for {}.",
                    transaction.getIdentifier(),
                    e.getPath(),
                    e);
                throw new TransactionCommitFailedException("Data did not pass validation.", e);
              }
            }
          });
    }

    @Override
    public ListenableFuture<Void> preCommit() {
      return executor.submit(
          new Callable<Void>() {
            @Override
            public Void call() {
              candidate = dataTree.prepare(modification);
              listenerResolver = ResolveDataChangeEventsTask.create(candidate, listenerTree);
              return null;
            }
          });
    }

    @Override
    public ListenableFuture<Void> abort() {
      candidate = null;
      return Futures.immediateFuture(null);
    }

    @Override
    public ListenableFuture<Void> commit() {
      checkState(candidate != null, "Proposed subtree must be computed");

      /*
       * The commit has to occur atomically with regard to listener
       * registrations.
       */
      synchronized (this) {
        dataTree.commit(candidate);

        for (ChangeListenerNotifyTask task : listenerResolver.call()) {
          LOG.trace("Scheduling invocation of listeners: {}", task);
          executor.submit(task);
        }
      }

      return Futures.immediateFuture(null);
    }
  }
}