/** {@inheritDoc} */
    @Override
    public boolean apply(Event evt) {
      assert evt.type() == EVT_CACHE_OBJECT_PUT || evt.type() == EVT_CACHE_OBJECT_REMOVED
          : "Unexpected event type: " + evt;

      info("Grid cache event: " + evt);

      latch.countDown();

      return true;
    }
        @Override
        public void onEvent(Event evt) {
          assert evt instanceof DiscoveryEvent;
          assert evt.type() == EVT_NODE_FAILED || evt.type() == EVT_NODE_LEFT;

          DiscoveryEvent discoEvt = (DiscoveryEvent) evt;

          if (log.isDebugEnabled())
            log.debug("Processing node left [nodeId=" + discoEvt.eventNode().id() + "]");

          for (Collection<GridCacheFuture<?>> futsCol : futs.values()) {
            for (GridCacheFuture<?> fut : futsCol) {
              if (!fut.trackable()) {
                if (log.isDebugEnabled()) log.debug("Skipping non-trackable future: " + fut);

                continue;
              }

              fut.onNodeLeft(discoEvt.eventNode().id());

              if (fut.isCancelled() || fut.isDone()) removeFuture(fut);
            }
          }

          for (IgniteInternalFuture<?> fut : atomicFuts.values()) {
            if (fut instanceof GridCacheFuture) {
              GridCacheFuture cacheFut = (GridCacheFuture) fut;

              cacheFut.onNodeLeft(discoEvt.eventNode().id());

              if (cacheFut.isCancelled() || cacheFut.isDone()) {
                GridCacheVersion futVer = cacheFut.version();

                if (futVer != null) atomicFuts.remove(futVer, fut);
              }
            }
          }
        }
    /** {@inheritDoc} */
    @Override
    public boolean apply(Event evt) {
      info("Received cache event: " + evt);

      if (evt instanceof CacheEvent) {
        CacheEvent cacheEvt = (CacheEvent) evt;

        Integer key = cacheEvt.key();

        if (keys.contains(key)) if (evt.type() == EVT_CACHE_OBJECT_UNLOCKED) latch.countDown();
      }

      return true;
    }
  /**
   * Grabs local events and detects if events was lost since last poll.
   *
   * @param ignite Target grid.
   * @param evtOrderKey Unique key to take last order key from node local map.
   * @param evtThrottleCntrKey Unique key to take throttle count from node local map.
   * @param evtTypes Event types to collect.
   * @param evtMapper Closure to map grid events to Visor data transfer objects.
   * @return Collections of node events
   */
  public static Collection<VisorGridEvent> collectEvents(
      Ignite ignite,
      String evtOrderKey,
      String evtThrottleCntrKey,
      final int[] evtTypes,
      IgniteClosure<Event, VisorGridEvent> evtMapper) {
    assert ignite != null;
    assert evtTypes != null && evtTypes.length > 0;

    ConcurrentMap<String, Long> nl = ignite.cluster().nodeLocalMap();

    final long lastOrder = getOrElse(nl, evtOrderKey, -1L);
    final long throttle = getOrElse(nl, evtThrottleCntrKey, 0L);

    // When we first time arrive onto a node to get its local events,
    // we'll grab only last those events that not older than given period to make sure we are
    // not grabbing GBs of data accidentally.
    final long notOlderThan = System.currentTimeMillis() - EVENTS_COLLECT_TIME_WINDOW;

    // Flag for detecting gaps between events.
    final AtomicBoolean lastFound = new AtomicBoolean(lastOrder < 0);

    IgnitePredicate<Event> p =
        new IgnitePredicate<Event>() {
          /** */
          private static final long serialVersionUID = 0L;

          @Override
          public boolean apply(Event e) {
            // Detects that events were lost.
            if (!lastFound.get() && (lastOrder == e.localOrder())) lastFound.set(true);

            // Retains events by lastOrder, period and type.
            return e.localOrder() > lastOrder
                && e.timestamp() > notOlderThan
                && F.contains(evtTypes, e.type());
          }
        };

    Collection<Event> evts = ignite.events().localQuery(p);

    // Update latest order in node local, if not empty.
    if (!evts.isEmpty()) {
      Event maxEvt = Collections.max(evts, EVTS_ORDER_COMPARATOR);

      nl.put(evtOrderKey, maxEvt.localOrder());
    }

    // Update throttle counter.
    if (!lastFound.get())
      nl.put(evtThrottleCntrKey, throttle == 0 ? EVENTS_LOST_THROTTLE : throttle - 1);

    boolean lost = !lastFound.get() && throttle == 0;

    Collection<VisorGridEvent> res = new ArrayList<>(evts.size() + (lost ? 1 : 0));

    if (lost) res.add(new VisorGridEventsLost(ignite.cluster().localNode().id()));

    for (Event e : evts) {
      VisorGridEvent visorEvt = evtMapper.apply(e);

      if (visorEvt != null) res.add(visorEvt);
    }

    return res;
  }
 @Override
 public int compare(Event o1, Event o2) {
   return Long.compare(o1.localOrder(), o2.localOrder());
 }