package edu.uw.zookeeper.server; import java.util.Iterator; import java.util.concurrent.ConcurrentMap; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.MapMaker; import com.google.common.collect.Multimaps; import com.google.common.collect.SetMultimap; import edu.uw.zookeeper.common.Processors; import edu.uw.zookeeper.common.Processors.ForwardingProcessor; import edu.uw.zookeeper.data.CreateFlag; import edu.uw.zookeeper.data.CreateMode; import edu.uw.zookeeper.data.Operations; import edu.uw.zookeeper.data.TxnOperation; import edu.uw.zookeeper.data.TxnRequest; import edu.uw.zookeeper.data.ZNodePath; import edu.uw.zookeeper.protocol.Operation; import edu.uw.zookeeper.protocol.ProtocolRequestMessage; import edu.uw.zookeeper.protocol.SessionRequest; import edu.uw.zookeeper.protocol.proto.IMultiRequest; import edu.uw.zookeeper.protocol.proto.IMultiResponse; import edu.uw.zookeeper.protocol.proto.OpCode; import edu.uw.zookeeper.protocol.proto.Records; public class EphemeralProcessor extends ForwardingProcessor<TxnOperation.Request<?>, Records.Response> implements Processors.UncheckedProcessor<TxnOperation.Request<?>, Records.Response> { public static EphemeralProcessor create( Processors.UncheckedProcessor<TxnOperation.Request<?>, Records.Response> delegate) { return new EphemeralProcessor(delegate); } protected final Processors.UncheckedProcessor<TxnOperation.Request<?>, Records.Response> delegate; protected final SetMultimap<Long, String> bySession; protected final ConcurrentMap<String, Long> byPath; public EphemeralProcessor( Processors.UncheckedProcessor<TxnOperation.Request<?>, Records.Response> delegate) { this.delegate = delegate; this.bySession = Multimaps.synchronizedSetMultimap(HashMultimap.<Long, String>create()); this.byPath = new MapMaker().makeMap(); } @Override public Records.Response apply(TxnOperation.Request<?> input) { Records.Request request = input.record(); Records.Response response = delegate().apply(input); Long session = input.getSessionId(); if (request.opcode() == OpCode.CLOSE_SESSION) { if (! (response instanceof Operation.Error)) { for (String path: ImmutableSet.copyOf(bySession.get(session))) { apply(TxnRequest.of( input.getTime(), input.zxid(), SessionRequest.of( input.getSessionId(), ProtocolRequestMessage.of( input.xid(), Operations.Requests.delete().setPath(ZNodePath.fromString(path)).build())))); } } } else { apply(session, request, response); } return response; } protected Records.Response apply(Long session, Records.Request request, Records.Response response) { switch (response.opcode()) { case CREATE: case CREATE2: { CreateMode mode = CreateMode.valueOf(((Records.CreateModeGetter) request).getFlags()); if (mode.contains(CreateFlag.EPHEMERAL)) { String path = ((Records.PathGetter) response).getPath(); bySession.put(session, path); byPath.put(path, session); } break; } case DELETE: { String path = ((Records.PathGetter) request).getPath(); Long owner = byPath.remove(path); if (owner != null) { bySession.remove(owner, path); } break; } case MULTI: { Iterator<Records.MultiOpRequest> requests = ((IMultiRequest) request).iterator(); Iterator<Records.MultiOpResponse> responses = ((IMultiResponse) response).iterator(); while (requests.hasNext()) { apply(session, requests.next(), responses.next()); } break; } case CLOSE_SESSION: { throw new AssertionError(request.toString()); } default: break; } return response; } @Override protected Processors.UncheckedProcessor<TxnOperation.Request<?>, Records.Response> delegate() { return delegate; } }