private void raiseEventForInitialTransfer(UUID identifier, CacheEntry entry, boolean clustered) {
    EventImpl preEvent;
    if (clustered) {
      // In clustered mode we only send post event
      preEvent = null;
    } else {
      preEvent = EventImpl.createEvent(cache, CACHE_ENTRY_CREATED);
      preEvent.setKey(entry.getKey());
      preEvent.setPre(true);
    }

    EventImpl postEvent = EventImpl.createEvent(cache, CACHE_ENTRY_CREATED);
    postEvent.setKey(entry.getKey());
    postEvent.setValue(entry.getValue());
    postEvent.setMetadata(entry.getMetadata());
    postEvent.setPre(false);

    for (CacheEntryListenerInvocation<K, V> invocation : cacheEntryCreatedListeners) {
      // Now notify all our methods of the creates
      if (invocation.getIdentifier() == identifier) {
        if (preEvent != null) {
          // Non clustered notifications are done twice
          invocation.invokeNoChecks(preEvent, true, true);
        }
        invocation.invokeNoChecks(postEvent, true, true);
      }
    }
  }
  private void configureEvent(
      EventImpl<K, V> e,
      K key,
      V value,
      boolean pre,
      InvocationContext ctx,
      FlagAffectedCommand command,
      V previousValue,
      Metadata previousMetadata) {
    boolean originLocal = ctx.isOriginLocal();

    e.setOriginLocal(originLocal);
    e.setValue(pre ? previousValue : value);
    e.setPre(pre);
    e.setOldValue(previousValue);
    e.setOldMetadata(previousMetadata);
    CacheEntry entry = ctx.lookupEntry(key);
    if (entry != null) {
      e.setMetadata(entry.getMetadata());
    }
    Set<Flag> flags;
    if (command != null
        && (flags = command.getFlags()) != null
        && flags.contains(Flag.COMMAND_RETRY)) {
      e.setCommandRetried(true);
    }
    e.setKey(key);
    setTx(ctx, e);
  }
 private boolean needsRemoteGet(InvocationContext ctx, Object key, boolean retvalCheck) {
   final CacheEntry entry;
   return retvalCheck
       && !ctx.hasFlag(Flag.CACHE_MODE_LOCAL)
       && !ctx.hasFlag(Flag.SKIP_REMOTE_LOOKUP)
       && ((entry = ctx.lookupEntry(key)) == null || entry.isNull() || entry.isLockPlaceholder());
 }
  @Override
  public final CacheEntry wrapEntryForReading(InvocationContext ctx, Object key)
      throws InterruptedException {
    CacheEntry cacheEntry = getFromContext(ctx, key);
    if (cacheEntry == null) {
      cacheEntry = getFromContainer(key);

      // do not bother wrapping though if this is not in a tx.  repeatable read etc are all
      // meaningless unless there is a tx.
      if (useRepeatableRead) {
        MVCCEntry mvccEntry =
            cacheEntry == null
                ? createWrappedEntry(key, null, null, false, false, -1)
                : createWrappedEntry(
                    key,
                    cacheEntry.getValue(),
                    cacheEntry.getVersion(),
                    false,
                    false,
                    cacheEntry.getLifespan());
        if (mvccEntry != null) ctx.putLookedUpEntry(key, mvccEntry);
        return mvccEntry;
      } else if (cacheEntry
          != null) { // if not in transaction and repeatable read, or simply read committed
                     // (regardless of whether in TX or not), do not wrap
        ctx.putLookedUpEntry(key, cacheEntry);
      }
      return cacheEntry;
    }
    return cacheEntry;
  }
  @Override
  public CompletableFuture<Void> visitReadOnlyKeyCommand(
      InvocationContext ctx, ReadOnlyKeyCommand command) throws Throwable {
    try {
      CacheEntry entry = entryFactory.wrapEntryForReading(ctx, command.getKey(), null);
      // Null entry is often considered to mean that entry is not available
      // locally, but if there's no need to get remote, the read-only
      // function needs to be executed, so force a non-null entry in
      // context with null content
      if (entry == null && cdl.localNodeIsOwner(command.getKey())) {
        entryFactory.wrapEntryForReading(ctx, command.getKey(), NullCacheEntry.getInstance());
      }

      return ctx.shortCircuit(ctx.forkInvocationSync(command));
    } finally {
      // needed because entries might be added in L1
      if (!ctx.isInTxScope()) commitContextEntries(ctx, command, null);
      else {
        CacheEntry entry = ctx.lookupEntry(command.getKey());
        if (entry != null) {
          entry.setSkipLookup(true);
        }
      }
    }
  }
    @Override
    protected void commitSingleEntry(
        CacheEntry entry,
        Metadata metadata,
        FlagAffectedCommand command,
        InvocationContext ctx,
        Flag trackFlag,
        boolean l1Invalidation) {
      // Cache flags before they're reset
      // TODO: Can the reset be done after notification instead?
      boolean created = entry.isCreated();
      boolean removed = entry.isRemoved();
      boolean expired;
      if (removed && entry instanceof MVCCEntry) {
        expired = ((MVCCEntry) entry).isExpired();
      } else {
        expired = false;
      }

      InternalCacheEntry previousEntry = dataContainer.peek(entry.getKey());
      Object previousValue = null;
      Metadata previousMetadata = null;
      if (previousEntry != null) {
        previousValue = previousEntry.getValue();
        previousMetadata = previousEntry.getMetadata();
      }
      commitManager.commit(entry, metadata, trackFlag, l1Invalidation);

      // Notify after events if necessary
      notifyCommitEntry(
          created, removed, expired, entry, ctx, command, previousValue, previousMetadata);
    }
 public static Collection getInternalValues(Cache cache) {
   DataContainer dataContainer = TestingUtil.extractComponent(cache, DataContainer.class);
   Collection values = new ArrayList();
   for (CacheEntry entry : dataContainer) {
     values.add(entry.getValue());
   }
   return values;
 }
 public static Set getInternalKeys(Cache cache) {
   DataContainer dataContainer = TestingUtil.extractComponent(cache, DataContainer.class);
   Set keys = new HashSet();
   for (CacheEntry entry : dataContainer) {
     keys.add(entry.getKey());
   }
   return keys;
 }
 /**
  * Locks the value for the keys accessed by the command to avoid being override from a remote get.
  */
 private CompletableFuture<Void> setSkipRemoteGetsAndInvokeNextForDataCommand(
     InvocationContext context, DataWriteCommand command, Metadata metadata) throws Throwable {
   Object retVal = invokeNextAndApplyChanges(context, command, metadata);
   if (context.isInTxScope()) {
     CacheEntry entry = context.lookupEntry(command.getKey());
     if (entry != null) {
       entry.setSkipLookup(true);
     }
   }
   return context.shortCircuit(retVal);
 }
 private DeltaAwareCacheEntry wrapInternalCacheEntryForDelta(
     InvocationContext ctx, Object key, CacheEntry cacheEntry) {
   DeltaAwareCacheEntry e;
   if (cacheEntry instanceof MVCCEntry) {
     e = createWrappedDeltaEntry(key, (DeltaAware) cacheEntry.getValue(), cacheEntry);
   } else {
     e = createWrappedDeltaEntry(key, (DeltaAware) cacheEntry.getValue(), null);
   }
   ctx.putLookedUpEntry(key, e);
   return e;
 }
 public static String printCache(Cache cache) {
   DataContainer dataContainer = TestingUtil.extractComponent(cache, DataContainer.class);
   Iterator it = dataContainer.iterator();
   StringBuilder builder = new StringBuilder(cache.getName() + "[");
   while (it.hasNext()) {
     CacheEntry ce = (CacheEntry) it.next();
     builder.append(ce.getKey() + "=" + ce.getValue() + ",l=" + ce.getLifespan() + "; ");
   }
   builder.append("]");
   return builder.toString();
 }
 @Override
 public Object perform(InvocationContext ctx) throws Throwable {
   CacheEntry entry = ctx.lookupEntry(key);
   if (entry == null || entry.isNull()) {
     return null;
   }
   if (entry.isRemoved()) {
     return null;
   }
   return entryFactory.copy(entry);
 }
 private MVCCEntry wrapMvccEntryForRemove(
     InvocationContext ctx, Object key, CacheEntry cacheEntry) {
   MVCCEntry mvccEntry =
       createWrappedEntry(
           key,
           cacheEntry.getValue(),
           cacheEntry.getVersion(),
           false,
           true,
           cacheEntry.getLifespan());
   ctx.putLookedUpEntry(key, mvccEntry);
   return mvccEntry;
 }
 /**
  * Locks the value for the keys accessed by the command to avoid being override from a remote get.
  */
 private CompletableFuture<Void> setSkipRemoteGetsAndInvokeNextForPutMapCommand(
     InvocationContext context, WriteCommand command) throws Throwable {
   Object retVal = invokeNextAndApplyChanges(context, command, command.getMetadata());
   if (context.isInTxScope()) {
     for (Object key : command.getAffectedKeys()) {
       CacheEntry entry = context.lookupEntry(key);
       if (entry != null) {
         entry.setSkipLookup(true);
       }
     }
   }
   return context.shortCircuit(retVal);
 }
  public void testWriteLockIsAcquired() throws Exception {
    advancedCache.put("k", "v");
    assertNotLocked(advancedCache, "k");
    tm.begin();
    advancedCache.withFlags(Flag.FORCE_WRITE_LOCK).get("k");

    InvocationContext ic = advancedCache.getInvocationContextContainer().getInvocationContext(true);
    CacheEntry cacheEntry = ic.getLookedUpEntries().get("k");
    assert (cacheEntry instanceof ReadCommittedEntry && cacheEntry.isChanged());

    assertLocked(advancedCache, "k");
    tm.commit();
    assertNotLocked(advancedCache, "k");
  }
  protected Object performRemove(CacheEntry e, InvocationContext ctx) {
    final Object removedValue = e.getValue();
    notify(ctx, removedValue, e.getMetadata(), true);

    e.setRemoved(true);
    e.setValid(false);
    e.setChanged(true);

    if (valueMatcher != ValueMatcher.MATCH_EXPECTED_OR_NEW) {
      return isConditional() ? true : removedValue;
    } else {
      // Return the expected value when retrying
      return isConditional() ? true : value;
    }
  }
 @Override
 protected void commitContextEntry(
     CacheEntry entry, InvocationContext ctx, FlagAffectedCommand command) {
   if (ctx.isInTxScope() && !isFromStateTransfer(ctx)) {
     EntryVersion version =
         ((TxInvocationContext) ctx)
             .getCacheTransaction()
             .getUpdatedEntryVersions()
             .get(entry.getKey());
     cdl.commitEntry(entry, version, command, ctx);
   } else {
     // This could be a state transfer call!
     cdl.commitEntry(entry, entry.getVersion(), command, ctx);
   }
 }
  @Override
  protected void commitContextEntry(
      CacheEntry entry,
      InvocationContext ctx,
      FlagAffectedCommand command,
      Metadata metadata,
      Flag stateTransferFlag,
      boolean l1Invalidation) {
    if (ctx.isInTxScope() && stateTransferFlag == null) {
      Metadata commitMetadata;
      // If user provided version, use it, otherwise generate/increment accordingly
      ClusteredRepeatableReadEntry clusterMvccEntry = (ClusteredRepeatableReadEntry) entry;
      EntryVersion existingVersion = clusterMvccEntry.getMetadata().version();
      EntryVersion newVersion;
      if (existingVersion == null) {
        newVersion = versionGenerator.generateNew();
      } else {
        newVersion = versionGenerator.increment((IncrementableEntryVersion) existingVersion);
      }

      if (metadata == null)
        commitMetadata = new EmbeddedMetadata.Builder().version(newVersion).build();
      else commitMetadata = metadata.builder().version(newVersion).build();

      cdl.commitEntry(entry, commitMetadata, command, ctx, null, l1Invalidation);
    } else {
      // This could be a state transfer call!
      cdl.commitEntry(entry, entry.getMetadata(), command, ctx, stateTransferFlag, l1Invalidation);
    }
  }
 private CompletableFuture<Void> visitDataReadCommand(
     InvocationContext ctx, AbstractDataCommand command) throws Throwable {
   try {
     entryFactory.wrapEntryForReading(ctx, command.getKey(), null);
     return ctx.shortCircuit(ctx.forkInvocationSync(command));
   } finally {
     // needed because entries might be added in L1
     if (!ctx.isInTxScope()) commitContextEntries(ctx, command, null);
     else {
       CacheEntry entry = ctx.lookupEntry(command.getKey());
       if (entry != null) {
         entry.setSkipLookup(true);
       }
     }
   }
 }
 /**
  * It tries to commit the cache entry. The entry is not committed if it is originated from state
  * transfer and other operation already has updated it.
  *
  * @param entry the entry to commit
  * @param metadata the entry's metadata
  * @param operation if {@code null}, it identifies this commit as originated from a normal
  *     operation. Otherwise, it is originated from a state transfer (local or remote site)
  */
 public final void commit(
     final CacheEntry entry,
     final Metadata metadata,
     final Flag operation,
     boolean l1Invalidation) {
   if (trace) {
     log.tracef(
         "Trying to commit. Key=%s. Operation Flag=%s, L1 invalidation=%s",
         toStr(entry.getKey()), operation, l1Invalidation);
   }
   if (l1Invalidation || (operation == null && !trackStateTransfer && !trackXSiteStateTransfer)) {
     // track == null means that it is a normal put and the tracking is not enabled!
     // if it is a L1 invalidation, commit without track it.
     if (trace) {
       log.tracef(
           "Committing key=%s. It is a L1 invalidation or a normal put and no tracking is enabled!",
           toStr(entry.getKey()));
     }
     entry.commit(dataContainer, metadata);
     return;
   }
   if (isTrackDisabled(operation)) {
     // this a put for state transfer but we are not tracking it. This means that the state
     // transfer has ended
     // or canceled due to a clear command.
     if (trace) {
       log.tracef(
           "Not committing key=%s. It is a state transfer key but no track is enabled!",
           toStr(entry.getKey()));
     }
     return;
   }
   tracker.compute(
       entry.getKey(),
       (o, discardPolicy) -> {
         if (discardPolicy != null && discardPolicy.ignore(operation)) {
           if (trace) {
             log.tracef(
                 "Not committing key=%s. It was already overwritten! Discard policy=%s",
                 toStr(entry.getKey()), discardPolicy);
           }
           return discardPolicy;
         }
         entry.commit(dataContainer, metadata);
         DiscardPolicy newDiscardPolicy = calculateDiscardPolicy();
         if (trace) {
           log.tracef(
               "Committed key=%s. Old discard policy=%s. New discard policy=%s",
               toStr(entry.getKey()), discardPolicy, newDiscardPolicy);
         }
         return newDiscardPolicy;
       });
 }
 @Override
 public CompletableFuture<Void> visitReadOnlyManyCommand(
     InvocationContext ctx, ReadOnlyManyCommand command) throws Throwable {
   try {
     for (Object key : command.getKeys()) {
       entryFactory.wrapEntryForReading(ctx, key, null);
     }
     return ctx.shortCircuit(ctx.forkInvocationSync(command));
   } finally {
     if (ctx.isInTxScope()) {
       for (Object key : command.getKeys()) {
         CacheEntry entry = ctx.lookupEntry(key);
         if (entry != null) {
           entry.setSkipLookup(true);
         }
       }
     }
   }
 }
  @Override
  public final MVCCEntry wrapEntryForPut(
      InvocationContext ctx, Object key, InternalCacheEntry icEntry, boolean undeleteIfNeeded)
      throws InterruptedException {
    CacheEntry cacheEntry = getFromContext(ctx, key);
    MVCCEntry mvccEntry;
    if (cacheEntry != null && cacheEntry.isNull()) cacheEntry = null;
    if (cacheEntry != null) {
      mvccEntry = wrapMvccEntryForPut(ctx, key, cacheEntry);
      mvccEntry.undelete(undeleteIfNeeded);
    } else {
      InternalCacheEntry ice = (icEntry == null ? getFromContainer(key) : icEntry);
      // A putForExternalRead is putIfAbsent, so if key present, do nothing
      if (ice != null && ctx.hasFlag(Flag.PUT_FOR_EXTERNAL_READ)) return null;

      mvccEntry =
          ice != null ? wrapInternalCacheEntryForPut(ctx, key, ice) : newMvccEntryForPut(ctx, key);
    }
    mvccEntry.copyForUpdate(container, localModeWriteSkewCheck);
    return mvccEntry;
  }
  private Object realRemoteGet(
      InvocationContext ctx, Object key, boolean storeInL1, boolean isWrite) throws Throwable {
    if (trace) log.tracef("Doing a remote get for key %s", key);

    boolean acquireRemoteLock = false;
    if (ctx.isInTxScope()) {
      TxInvocationContext txContext = (TxInvocationContext) ctx;
      acquireRemoteLock =
          isWrite && isPessimisticCache && !txContext.getAffectedKeys().contains(key);
    }
    // attempt a remote lookup
    InternalCacheEntry ice = dm.retrieveFromRemoteSource(key, ctx, acquireRemoteLock);

    if (acquireRemoteLock) {
      ((TxInvocationContext) ctx).addAffectedKey(key);
    }

    if (ice != null) {
      if (storeInL1) {
        if (isL1CacheEnabled) {
          if (trace) log.tracef("Caching remotely retrieved entry for key %s in L1", key);
          // This should be fail-safe
          try {
            long lifespan =
                ice.getLifespan() < 0
                    ? configuration.getL1Lifespan()
                    : Math.min(ice.getLifespan(), configuration.getL1Lifespan());
            PutKeyValueCommand put =
                cf.buildPutKeyValueCommand(
                    ice.getKey(), ice.getValue(), lifespan, -1, ctx.getFlags());
            lockAndWrap(ctx, key, ice);
            invokeNextInterceptor(ctx, put);
          } catch (Exception e) {
            // Couldn't store in L1 for some reason.  But don't fail the transaction!
            log.infof("Unable to store entry %s in L1 cache", key);
            log.debug("Inability to store in L1 caused by", e);
          }
        } else {
          CacheEntry ce = ctx.lookupEntry(key);
          if (ce == null || ce.isNull() || ce.isLockPlaceholder() || ce.getValue() == null) {
            if (ce != null && ce.isChanged()) {
              ce.setValue(ice.getValue());
            } else {
              if (isWrite) lockAndWrap(ctx, key, ice);
              else ctx.putLookedUpEntry(key, ice);
            }
          }
        }
      } else {
        if (trace) log.tracef("Not caching remotely retrieved entry for key %s in L1", key);
      }
      return ice.getValue();
    }
    return null;
  }
  @Override
  protected void commitContextEntry(
      CacheEntry entry, InvocationContext ctx, boolean skipOwnershipCheck) {
    if (ctx.isInTxScope()) {
      ClusteredRepeatableReadEntry clusterMvccEntry = (ClusteredRepeatableReadEntry) entry;
      EntryVersion existingVersion = clusterMvccEntry.getVersion();

      EntryVersion newVersion;
      if (existingVersion == null) {
        newVersion = versionGenerator.generateNew();
      } else {
        newVersion = versionGenerator.increment((IncrementableEntryVersion) existingVersion);
      }
      commitEntry(entry, newVersion, skipOwnershipCheck);
    } else {
      // This could be a state transfer call!
      commitEntry(entry, entry.getVersion(), skipOwnershipCheck);
    }
  }
  private boolean commitEntryIfNeeded(
      final InvocationContext ctx,
      final FlagAffectedCommand command,
      final CacheEntry entry,
      final Flag stateTransferFlag,
      final Metadata metadata) {
    if (entry == null) {
      return false;
    }
    final boolean l1Invalidation = command instanceof InvalidateL1Command;

    if (entry.isChanged()) {
      if (trace) log.tracef("About to commit entry %s", entry);
      commitContextEntry(entry, ctx, command, metadata, stateTransferFlag, l1Invalidation);

      return true;
    }
    return false;
  }
 public void transformForL1(CacheEntry entry) {
   if (entry.getLifespan() < 0 || entry.getLifespan() > configuration.getL1Lifespan())
     entry.setLifespan(configuration.getL1Lifespan());
 }
  /**
   * Adds the listener using the provided filter converter and class loader. The provided builder is
   * used to add additional configuration including (clustered, onlyPrimary & identifier) which can
   * be used after this method is completed to see what values were used in the addition of this
   * listener
   *
   * @param listener
   * @param filter
   * @param converter
   * @param classLoader
   * @param <C>
   * @return
   */
  public <C> void addListener(
      Object listener,
      CacheEventFilter<? super K, ? super V> filter,
      CacheEventConverter<? super K, ? super V, C> converter,
      ClassLoader classLoader) {
    Listener l = testListenerClassValidity(listener.getClass());
    UUID generatedId = UUID.randomUUID();
    CacheMode cacheMode = config.clustering().cacheMode();
    CacheInvocationBuilder builder = new CacheInvocationBuilder();
    builder
        .setIncludeCurrentState(l.includeCurrentState())
        .setClustered(l.clustered())
        .setOnlyPrimary(
            l.clustered() ? (cacheMode.isDistributed() ? true : false) : l.primaryOnly())
        .setFilter(filter)
        .setConverter(converter)
        .setIdentifier(generatedId)
        .setClassLoader(classLoader);
    boolean foundMethods = validateAndAddListenerInvocation(listener, builder);

    if (foundMethods && l.clustered()) {
      if (cacheMode.isInvalidation()) {
        throw new UnsupportedOperationException(
            "Cluster listeners cannot be used with Invalidation Caches!");
      } else if (cacheMode.isDistributed()) {
        clusterListenerIDs.put(listener, generatedId);
        EmbeddedCacheManager manager = cache.getCacheManager();
        Address ourAddress = manager.getAddress();

        List<Address> members = manager.getMembers();
        // If we are the only member don't even worry about sending listeners
        if (members != null && members.size() > 1) {
          DistributedExecutionCompletionService decs =
              new DistributedExecutionCompletionService(distExecutorService);

          if (log.isTraceEnabled()) {
            log.tracef(
                "Replicating cluster listener to other nodes %s for cluster listener with id %s",
                members, generatedId);
          }
          Callable callable =
              new ClusterListenerReplicateCallable(
                  generatedId, ourAddress, filter, converter, l.sync());
          for (Address member : members) {
            if (!member.equals(ourAddress)) {
              decs.submit(member, callable);
            }
          }

          for (int i = 0; i < members.size() - 1; ++i) {
            try {
              decs.take().get();
            } catch (InterruptedException e) {
              throw new CacheListenerException(e);
            } catch (ExecutionException e) {
              throw new CacheListenerException(e);
            }
          }

          int extraCount = 0;
          // If anyone else joined since we sent these we have to send the listeners again, since
          // they may have queried
          // before the other nodes got the new listener
          List<Address> membersAfter = manager.getMembers();
          for (Address member : membersAfter) {
            if (!members.contains(member) && !member.equals(ourAddress)) {
              if (log.isTraceEnabled()) {
                log.tracef(
                    "Found additional node %s that joined during replication of cluster listener with id %s",
                    member, generatedId);
              }
              extraCount++;
              decs.submit(member, callable);
            }
          }

          for (int i = 0; i < extraCount; ++i) {
            try {
              decs.take().get();
            } catch (InterruptedException e) {
              throw new CacheListenerException(e);
            } catch (ExecutionException e) {
              throw new CacheListenerException(e);
            }
          }
        }
      }
    }

    // If we have a segment listener handler, it means we have to do initial state
    QueueingSegmentListener handler = segmentHandler.remove(generatedId);
    if (handler != null) {
      if (log.isTraceEnabled()) {
        log.tracef("Listener %s requests initial state for cache", generatedId);
      }
      try (CloseableIterator<CacheEntry<K, C>> iterator =
          entryRetriever.retrieveEntries(
              filter == null ? null : new CacheEventFilterAsKeyValueFilter(filter),
              converter == null ? null : new CacheEventConverterAsConverter(converter),
              null,
              handler)) {
        while (iterator.hasNext()) {
          CacheEntry<K, C> entry = iterator.next();
          // Mark the key as processed and see if we had a concurrent update
          Object value = handler.markKeyAsProcessing(entry.getKey());
          if (value == BaseQueueingSegmentListener.REMOVED) {
            // Don't process this value if we had a concurrent remove
            continue;
          }
          raiseEventForInitialTransfer(generatedId, entry, builder.isClustered());

          handler.notifiedKey(entry.getKey());
        }
      }

      Set<CacheEntry> entries = handler.findCreatedEntries();

      for (CacheEntry entry : entries) {
        raiseEventForInitialTransfer(generatedId, entry, builder.isClustered());
      }

      if (log.isTraceEnabled()) {
        log.tracef("Listener %s initial state for cache completed", generatedId);
      }

      handler.transferComplete();
    }
  }
    protected void notifyCommitEntry(
        boolean created,
        boolean removed,
        boolean expired,
        CacheEntry entry,
        InvocationContext ctx,
        FlagAffectedCommand command,
        Object previousValue,
        Metadata previousMetadata) {
      boolean isWriteOnly =
          (command instanceof WriteCommand) && ((WriteCommand) command).isWriteOnly();
      if (removed) {
        if (command instanceof RemoveCommand) {
          ((RemoveCommand) command).notify(ctx, previousValue, previousMetadata, false);
        } else {
          if (expired) {
            notifier.notifyCacheEntryExpired(entry.getKey(), previousValue, previousMetadata, ctx);
          } else {
            notifier.notifyCacheEntryRemoved(
                entry.getKey(), previousValue, previousMetadata, false, ctx, command);
          }

          // A write-only command only writes and so can't 100% guarantee
          // to be able to retrieve previous value when removed, so only
          // send remove event when the command is read-write.
          if (!isWriteOnly)
            functionalNotifier.notifyOnRemove(
                EntryViews.readOnly(entry.getKey(), previousValue, previousMetadata));

          functionalNotifier.notifyOnWrite(() -> EntryViews.noValue(entry.getKey()));
        }
      } else {
        // Notify entry event after container has been updated
        if (created) {
          notifier.notifyCacheEntryCreated(
              entry.getKey(), entry.getValue(), entry.getMetadata(), false, ctx, command);

          // A write-only command only writes and so can't 100% guarantee
          // that an entry has been created, so only send create event
          // when the command is read-write.
          if (!isWriteOnly) functionalNotifier.notifyOnCreate(EntryViews.readOnly(entry));

          functionalNotifier.notifyOnWrite(() -> EntryViews.readOnly(entry));
        } else {
          notifier.notifyCacheEntryModified(
              entry.getKey(),
              entry.getValue(),
              entry.getMetadata(),
              previousValue,
              previousMetadata,
              false,
              ctx,
              command);

          // A write-only command only writes and so can't 100% guarantee
          // that an entry has been created, so only send modify when the
          // command is read-write.
          if (!isWriteOnly)
            functionalNotifier.notifyOnModify(
                EntryViews.readOnly(entry.getKey(), previousValue, previousMetadata),
                EntryViews.readOnly(entry));

          functionalNotifier.notifyOnWrite(() -> EntryViews.readOnly(entry));
        }
      }
    }
    @Override
    protected void commitSingleEntry(
        CacheEntry entry,
        Metadata metadata,
        FlagAffectedCommand command,
        InvocationContext ctx,
        Flag trackFlag,
        boolean l1Invalidation) {
      // Don't allow the CH to change (and state transfer to invalidate entries)
      // between the ownership check and the commit
      stateTransferLock.acquireSharedTopologyLock();
      try {
        boolean doCommit = true;
        // ignore locality for removals, even if skipOwnershipCheck is not true
        boolean skipOwnershipCheck = command != null && command.hasFlag(Flag.SKIP_OWNERSHIP_CHECK);

        boolean isForeignOwned = !skipOwnershipCheck && !localNodeIsOwner(entry.getKey());
        if (isForeignOwned && !entry.isRemoved()) {
          if (configuration.clustering().l1().enabled()) {
            // transform for L1
            long lifespan;
            if (metadata != null) {
              lifespan = metadata.lifespan();
            } else {
              lifespan = entry.getLifespan();
            }
            if (lifespan < 0 || lifespan > configuration.clustering().l1().lifespan()) {
              Metadata.Builder builder;
              if (metadata != null) {
                builder = metadata.builder();
              } else {
                builder = entry.getMetadata().builder();
              }
              metadata = builder.lifespan(configuration.clustering().l1().lifespan()).build();
            }
          } else {
            doCommit = false;
          }
        }

        boolean created = false;
        boolean removed = false;
        boolean expired = false;
        if (!isForeignOwned) {
          created = entry.isCreated();
          removed = entry.isRemoved();
          if (removed && entry instanceof MVCCEntry) {
            expired = ((MVCCEntry) entry).isExpired();
          }
        }

        if (doCommit) {
          InternalCacheEntry previousEntry = dataContainer.peek(entry.getKey());
          Object previousValue = null;
          Metadata previousMetadata = null;
          if (previousEntry != null) {
            previousValue = previousEntry.getValue();
            previousMetadata = previousEntry.getMetadata();
          }
          commitManager.commit(entry, metadata, trackFlag, l1Invalidation);
          if (!isForeignOwned) {
            notifyCommitEntry(
                created, removed, expired, entry, ctx, command, previousValue, previousMetadata);
          }
        } else entry.rollback();

      } finally {
        stateTransferLock.releaseSharedTopologyLock();
      }
    }
 @SuppressWarnings("UnusedParameters")
 protected EntryVersion getEntryVersion(InvocationContext ctx, Object key) {
   CacheEntry cacheEntry = dataContainer.get(key);
   return (cacheEntry != null) ? cacheEntry.getVersion() : null;
 }