package edu.uw.zookeeper.server; import java.util.Iterator; import java.util.List; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.Watcher.Event.EventType; import org.apache.zookeeper.Watcher.Event.KeeperState; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterators; import edu.uw.zookeeper.common.Processors; import edu.uw.zookeeper.common.Processors.ForwardingProcessor; import edu.uw.zookeeper.data.Operations; import edu.uw.zookeeper.data.ZNodeLabelVector; import edu.uw.zookeeper.data.TxnOperation; import edu.uw.zookeeper.protocol.Operation; import edu.uw.zookeeper.protocol.proto.IMultiRequest; import edu.uw.zookeeper.protocol.proto.IMultiResponse; import edu.uw.zookeeper.protocol.proto.IRemoveWatchesRequest; import edu.uw.zookeeper.protocol.proto.IWatcherEvent; import edu.uw.zookeeper.protocol.proto.OpCode; import edu.uw.zookeeper.protocol.proto.Records; public class WatcherEventProcessor extends ForwardingProcessor<TxnOperation.Request<?>, Records.Response> implements Processors.CheckedProcessor<TxnOperation.Request<?>, Records.Response, KeeperException> { public static WatcherEventProcessor create( Processors.CheckedProcessor<TxnOperation.Request<?>, ? extends Records.Response, KeeperException> delegate, Watches dataWatches, Watches childWatches) { return new WatcherEventProcessor(delegate, dataWatches, childWatches); } public static IWatcherEvent created(String path) { return of(EventType.NodeCreated.getIntValue(), KeeperState.SyncConnected.getIntValue(), path); } public static IWatcherEvent deleted(String path) { return of(EventType.NodeDeleted.getIntValue(), KeeperState.SyncConnected.getIntValue(), path); } public static IWatcherEvent data(String path) { return of(EventType.NodeDataChanged.getIntValue(), KeeperState.SyncConnected.getIntValue(), path); } public static IWatcherEvent children(String path) { return of(EventType.NodeChildrenChanged.getIntValue(), KeeperState.SyncConnected.getIntValue(), path); } public static IWatcherEvent of(int type, int state, String path) { return new IWatcherEvent(type, state, path); } protected final Processors.CheckedProcessor<TxnOperation.Request<?>, ? extends Records.Response, KeeperException> delegate; protected final Watches dataWatches; protected final Watches childWatches; protected WatcherEventProcessor( Processors.CheckedProcessor<TxnOperation.Request<?>, ? extends Records.Response, KeeperException> delegate, Watches dataWatches, Watches childWatches) { this.delegate = delegate; this.dataWatches = dataWatches; this.childWatches = childWatches; } @Override public Records.Response apply(TxnOperation.Request<?> input) throws KeeperException { KeeperException exception = null; Records.Response response = null; if (input.record().opcode() == OpCode.REMOVE_WATCHES) { response = Operations.Responses.removeWatches().build(); } else { try { response = delegate().apply(input); } catch (KeeperException e) { exception = e; response = null; } } return apply(input.getSessionId(), input.record(), response, exception); } protected Records.Response apply(Long session, Records.Request request, Records.Response response, KeeperException exception) throws KeeperException { switch (request.opcode()) { case GET_DATA: case EXISTS: if (((Records.WatchGetter) request).getWatch()) { dataWatches.put(session, ((Records.PathGetter) request).getPath()); } break; case GET_CHILDREN: case GET_CHILDREN2: if (((Records.WatchGetter) request).getWatch()) { childWatches.put(session, ((Records.PathGetter) request).getPath()); } break; case CREATE: case CREATE2: { if ((response != null) && !(response instanceof Operation.Error)) { String path = ((Records.PathGetter) response).getPath(); String parent = ZNodeLabelVector.headOf(path); dataWatches.post(created(path)); if (parent.length() > 0) { childWatches.post(children(parent)); } } break; } case DELETE: { if ((response != null) && !(response instanceof Operation.Error)) { String path = ((Records.PathGetter) request).getPath(); String parent = ZNodeLabelVector.headOf(path); dataWatches.post(deleted(path)); if (parent.length() > 0) { childWatches.post(children(parent)); } } break; } case SET_DATA: { if ((response != null) && !(response instanceof Operation.Error)) { String path = ((Records.PathGetter) request).getPath(); dataWatches.post(data(path)); } break; } case MULTI: { Iterator<? extends Records.MultiOpRequest> requests = ((IMultiRequest) request).iterator(); Iterator<? extends Records.MultiOpResponse> responses = (response != null) ? ((IMultiResponse) response).iterator() : Iterators.cycle(Operations.Responses.error().setError(exception.code()).build()); while (requests.hasNext()) { apply(session, requests.next(), responses.next(), exception); } break; } case REMOVE_WATCHES: { String path = ((IRemoveWatchesRequest) request).getPath(); List<Watches> types; switch (Watcher.WatcherType.fromInt(((IRemoveWatchesRequest) request).getType())) { case Any: types = ImmutableList.of(dataWatches, childWatches); break; case Children: types = ImmutableList.of(childWatches); break; case Data: types = ImmutableList.of(dataWatches); break; default: throw new AssertionError(); } boolean removed = false; for (Watches watches: types) { if (watches.byPath().remove(path, session)) { if (!removed) { removed = true; } } } if (!removed) { if (exception == null) { exception = new KeeperException.NoWatcherException(); } } break; } case CLOSE_SESSION: { dataWatches.remove(session); childWatches.remove(session); break; } default: break; } if (exception != null) { throw exception; } else { return response; } } @Override protected Processors.CheckedProcessor<TxnOperation.Request<?>, ? extends Records.Response, KeeperException> delegate() { return delegate; } }