private OperationFuture<String> createLiveNode() {
    String liveNode = getLiveNodePath();
    LOG.info("Create live node {}{}", zkClient.getConnectString(), liveNode);

    JsonObject content = new JsonObject();
    content.add("data", liveNodeData.get());
    return ZKOperations.ignoreError(
        zkClient.create(liveNode, encodeJson(content), CreateMode.EPHEMERAL),
        KeeperException.NodeExistsException.class,
        liveNode);
  }
 private void watchStateNode() {
   if (!shouldProcessZKEvent()) {
     return;
   }
   Futures.addCallback(
       zkClient.getData(
           getZKPath("state"),
           new Watcher() {
             @Override
             public void process(WatchedEvent event) {
               if (!shouldProcessZKEvent()) {
                 return;
               }
               switch (event.getType()) {
                 case NodeDataChanged:
                   watchStateNode();
                   break;
                 default:
                   LOG.debug("Ignore ZK event for state node: {}", event);
               }
             }
           }),
       stateNodeDataCallback,
       Threads.SAME_THREAD_EXECUTOR);
 }
 protected final void watchInstanceNode() {
   if (!shouldProcessZKEvent()) {
     return;
   }
   Futures.addCallback(
       zkClient.getData(
           getInstancePath(),
           new Watcher() {
             @Override
             public void process(WatchedEvent event) {
               if (!shouldProcessZKEvent()) {
                 return;
               }
               switch (event.getType()) {
                 case NodeDataChanged:
                   watchInstanceNode();
                   break;
                 case NodeDeleted:
                   instanceNodeFailed(
                       KeeperException.create(KeeperException.Code.NONODE, getInstancePath()));
                   break;
                 default:
                   LOG.info("Ignore ZK event for instance node: {}", event);
               }
             }
           }),
       instanceNodeDataCallback,
       Threads.SAME_THREAD_EXECUTOR);
 }
    public void onReceived(
        Executor executor,
        final String path,
        final int version,
        final String id,
        final Message message) {
      if (callback == null) {
        // Simply delete the message
        if (LOG.isDebugEnabled()) {
          LOG.debug("Ignoring incoming message from " + path + ": " + message);
        }
        listenFailure(zkClient.delete(path, version));
        return;
      }

      executor.execute(
          new Runnable() {
            @Override
            public void run() {
              try {
                // Message process is synchronous for now. Making it async needs more thoughts about
                // race conditions.
                // The executor is the callbackExecutor which is a single thread executor.
                callback.onReceived(id, message).get();
              } catch (Throwable t) {
                LOG.error("Exception when processing message: {}, {}, {}", id, message, path, t);
              } finally {
                listenFailure(zkClient.delete(path, version));
              }
            }
          });
    }
  private void processMessage(final String path, final String messageId) {
    Futures.addCallback(
        zkClient.getData(path),
        new FutureCallback<NodeData>() {
          @Override
          public void onSuccess(NodeData result) {
            Message message = MessageCodec.decode(result.getData());
            if (message == null) {
              LOG.error("Failed to decode message for " + messageId + " in " + path);
              listenFailure(zkClient.delete(path, result.getStat().getVersion()));
              return;
            }
            if (LOG.isDebugEnabled()) {
              LOG.debug(
                  "Message received from "
                      + path
                      + ": "
                      + new String(MessageCodec.encode(message), Charsets.UTF_8));
            }
            if (handleStopMessage(
                message, getDeleteSupplier(path, result.getStat().getVersion()))) {
              return;
            }
            messageCallback.onReceived(
                callbackExecutor, path, result.getStat().getVersion(), messageId, message);
          }

          @Override
          public void onFailure(Throwable t) {
            LOG.error("Failed to fetch message content.", t);
          }
        });
  }
  private void watchMessages() {
    final String messagesPath = getZKPath("messages");
    Futures.addCallback(
        zkClient.getChildren(
            messagesPath,
            new Watcher() {
              @Override
              public void process(WatchedEvent event) {
                // TODO: Do we need to deal with other type of events?
                if (event.getType() == Event.EventType.NodeChildrenChanged
                    && decoratedService.isRunning()) {
                  watchMessages();
                }
              }
            }),
        new FutureCallback<NodeChildren>() {
          @Override
          public void onSuccess(NodeChildren result) {
            // Sort by the name, which is the messageId. Assumption is that message ids is ordered
            // by time.
            List<String> messages = Lists.newArrayList(result.getChildren());
            Collections.sort(messages);
            for (String messageId : messages) {
              processMessage(messagesPath + "/" + messageId, messageId);
            }
          }

          @Override
          public void onFailure(Throwable t) {
            // TODO: what could be done besides just logging?
            LOG.error("Failed to watch messages.", t);
          }
        });
  }
  private void actOnExists(final String path, final Runnable action) {
    // Watch for node existence.
    final AtomicBoolean nodeExists = new AtomicBoolean(false);
    Futures.addCallback(
        zkClient.exists(
            path,
            new Watcher() {
              @Override
              public void process(WatchedEvent event) {
                if (!shouldProcessZKEvent()) {
                  return;
                }
                // When node is created, call the action.
                // Other event type would be handled by the action.
                if (event.getType() == Event.EventType.NodeCreated
                    && nodeExists.compareAndSet(false, true)) {
                  action.run();
                }
              }
            }),
        new FutureCallback<Stat>() {
          @Override
          public void onSuccess(Stat result) {
            if (result != null && nodeExists.compareAndSet(false, true)) {
              action.run();
            }
          }

          @Override
          public void onFailure(Throwable t) {
            LOG.error("Failed in exists call to {}. Shutting down service.", path, t);
            forceShutDown();
          }
        },
        Threads.SAME_THREAD_EXECUTOR);
  }
 private OperationFuture<String> removeServiceNode() {
   String serviceNode = String.format("/%s", id.getId());
   LOG.info("Remove service node {}{}", zkClient.getConnectString(), serviceNode);
   return ZKOperations.recursiveDelete(zkClient, serviceNode);
 }
 private OperationFuture<String> removeLiveNode() {
   String liveNode = getLiveNodePath();
   LOG.info("Remove live node {}{}", zkClient.getConnectString(), liveNode);
   return ZKOperations.ignoreError(
       zkClient.delete(liveNode), KeeperException.NoNodeException.class, liveNode);
 }
  @Override
  protected void doStart() {
    callbackExecutor =
        Executors.newSingleThreadExecutor(Threads.createDaemonThreadFactory("message-callback"));
    // Create the live node, if succeeded, start the decorated service, otherwise fail out.
    Futures.addCallback(
        createLiveNode(),
        new FutureCallback<String>() {
          @Override
          public void onSuccess(String result) {
            // Create nodes for states and messaging
            StateNode stateNode = new StateNode(ServiceController.State.STARTING);

            final ListenableFuture<List<String>> createFuture =
                Futures.allAsList(
                    ZKOperations.ignoreError(
                        zkClient.create(getZKPath("messages"), null, CreateMode.PERSISTENT),
                        KeeperException.NodeExistsException.class,
                        null),
                    zkClient.create(
                        getZKPath("state"), encodeStateNode(stateNode), CreateMode.PERSISTENT));

            createFuture.addListener(
                new Runnable() {
                  @Override
                  public void run() {
                    try {
                      createFuture.get();
                      // Starts the decorated service
                      decoratedService.addListener(createListener(), Threads.SAME_THREAD_EXECUTOR);
                      decoratedService.start();
                    } catch (Exception e) {
                      notifyFailed(e);
                    }
                  }
                },
                Threads.SAME_THREAD_EXECUTOR);
          }

          @Override
          public void onFailure(Throwable t) {
            notifyFailed(t);
          }
        });

    // Watch for session expiration, recreate the live node if reconnected after expiration.
    zkClient.addConnectionWatcher(
        new Watcher() {
          private boolean expired = false;

          @Override
          public void process(WatchedEvent event) {
            if (event.getState() == Event.KeeperState.Expired) {
              LOG.warn(
                  "ZK Session expired for service {} with runId {}.", decoratedService, id.getId());
              expired = true;
            } else if (event.getState() == Event.KeeperState.SyncConnected && expired) {
              LOG.info(
                  "Reconnected after expiration for service {} with runId {}",
                  decoratedService,
                  id.getId());
              expired = false;
              Futures.addCallback(
                  createLiveNode(),
                  new FutureCallback<String>() {
                    @Override
                    public void onSuccess(String result) {
                      // All good, no-op
                    }

                    @Override
                    public void onFailure(Throwable t) {
                      notifyFailed(t);
                    }
                  },
                  Threads.SAME_THREAD_EXECUTOR);
            }
          }
        });
  }