http://git-wip-us.apache.org/repos/asf/zookeeper/blob/43d71c2e/zookeeper-server/src/main/java/org/apache/zookeeper/Login.java
----------------------------------------------------------------------
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/Login.java 
b/zookeeper-server/src/main/java/org/apache/zookeeper/Login.java
new file mode 100644
index 0000000..d97d6c1
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/Login.java
@@ -0,0 +1,422 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zookeeper;
+
+/**
+ * This class is responsible for refreshing Kerberos credentials for
+ * logins for both Zookeeper client and server.
+ * See ZooKeeperSaslServer for server-side usage.
+ * See ZooKeeperSaslClient for client-side usage.
+ */
+
+import javax.security.auth.kerberos.KerberosPrincipal;
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.callback.CallbackHandler;
+
+import org.apache.zookeeper.client.ZKClientConfig;
+import org.apache.zookeeper.common.ZKConfig;
+import org.apache.zookeeper.server.ZooKeeperSaslServer;
+import org.apache.zookeeper.common.Time;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.security.auth.kerberos.KerberosTicket;
+import javax.security.auth.Subject;
+
+import java.util.Date;
+import java.util.Random;
+import java.util.Set;
+
+public class Login {
+    private static final String KINIT_COMMAND_DEFAULT = "/usr/bin/kinit";
+    private static final Logger LOG = LoggerFactory.getLogger(Login.class);
+    public CallbackHandler callbackHandler;
+
+    // LoginThread will sleep until 80% of time from last refresh to
+    // ticket's expiry has been reached, at which time it will wake
+    // and try to renew the ticket.
+    private static final float TICKET_RENEW_WINDOW = 0.80f;
+
+    /**
+     * Percentage of random jitter added to the renewal time
+     */
+    private static final float TICKET_RENEW_JITTER = 0.05f;
+
+    // Regardless of TICKET_RENEW_WINDOW setting above and the ticket expiry 
time,
+    // thread will not sleep between refresh attempts any less than 1 minute 
(60*1000 milliseconds = 1 minute).
+    // Change the '1' to e.g. 5, to change this to 5 minutes.
+    private static final long MIN_TIME_BEFORE_RELOGIN = 1 * 60 * 1000L;
+
+    private Subject subject = null;
+    private Thread t = null;
+    private boolean isKrbTicket = false;
+    private boolean isUsingTicketCache = false;
+
+    /** Random number generator */
+    private static Random rng = new Random();
+
+    private LoginContext login = null;
+    private String loginContextName = null;
+    private String principal = null;
+
+    // Initialize 'lastLogin' to do a login at first time
+    private long lastLogin = Time.currentElapsedTime() - 
MIN_TIME_BEFORE_RELOGIN;
+    private final ZKConfig zkConfig;
+
+    /**
+     * LoginThread constructor. The constructor starts the thread used to
+     * periodically re-login to the Kerberos Ticket Granting Server.
+     * 
+     * @param loginContextName
+     *            name of section in JAAS file that will be use to login. 
Passed
+     *            as first param to javax.security.auth.login.LoginContext().
+     *
+     * @param callbackHandler
+     *            Passed as second param to
+     *            javax.security.auth.login.LoginContext().
+     * @param zkConfig
+     *            client or server configurations
+     * @throws javax.security.auth.login.LoginException
+     *             Thrown if authentication fails.
+     */
+    public Login(final String loginContextName, CallbackHandler 
callbackHandler, final ZKConfig zkConfig)
+            throws LoginException {
+        this.zkConfig=zkConfig;
+        this.callbackHandler = callbackHandler;
+        login = login(loginContextName);
+        this.loginContextName = loginContextName;
+        subject = login.getSubject();
+        isKrbTicket = 
!subject.getPrivateCredentials(KerberosTicket.class).isEmpty();
+        AppConfigurationEntry entries[] = 
Configuration.getConfiguration().getAppConfigurationEntry(loginContextName);
+        for (AppConfigurationEntry entry: entries) {
+            // there will only be a single entry, so this for() loop will only 
be iterated through once.
+            if (entry.getOptions().get("useTicketCache") != null) {
+                String val = (String)entry.getOptions().get("useTicketCache");
+                if (val.equals("true")) {
+                    isUsingTicketCache = true;
+                }
+            }
+            if (entry.getOptions().get("principal") != null) {
+                principal = (String)entry.getOptions().get("principal");
+            }
+            break;
+        }
+
+        if (!isKrbTicket) {
+            // if no TGT, do not bother with ticket management.
+            return;
+        }
+
+        // Refresh the Ticket Granting Ticket (TGT) periodically. How often to 
refresh is determined by the
+        // TGT's existing expiry date and the configured 
MIN_TIME_BEFORE_RELOGIN. For testing and development,
+        // you can decrease the interval of expiration of tickets (for 
example, to 3 minutes) by running :
+        //  "modprinc -maxlife 3mins <principal>" in kadmin.
+        t = new Thread(new Runnable() {
+            public void run() {
+                LOG.info("TGT refresh thread started.");
+                while (true) {  // renewal thread's main loop. if it exits 
from here, thread will exit.
+                    KerberosTicket tgt = getTGT();
+                    long now = Time.currentWallTime();
+                    long nextRefresh;
+                    Date nextRefreshDate;
+                    if (tgt == null) {
+                        nextRefresh = now + MIN_TIME_BEFORE_RELOGIN;
+                        nextRefreshDate = new Date(nextRefresh);
+                        LOG.warn("No TGT found: will try again at {}", 
nextRefreshDate);
+                    } else {
+                        nextRefresh = getRefreshTime(tgt);
+                        long expiry = tgt.getEndTime().getTime();
+                        Date expiryDate = new Date(expiry);
+                        if ((isUsingTicketCache) && 
(tgt.getEndTime().equals(tgt.getRenewTill()))) {
+                            Object[] logPayload = {expiryDate, principal, 
principal};
+                            LOG.error("The TGT cannot be renewed beyond the 
next expiry date: {}." +
+                                    "This process will not be able to 
authenticate new SASL connections after that " +
+                                    "time (for example, it will not be 
authenticate a new connection with a Zookeeper " +
+                                    "Quorum member).  Ask your system 
administrator to either increase the " +
+                                    "'renew until' time by doing : 'modprinc 
-maxrenewlife {}' within " +
+                                    "kadmin, or instead, to generate a keytab 
for {}. Because the TGT's " +
+                                    "expiry cannot be further extended by 
refreshing, exiting refresh thread now.", logPayload);
+                            return;
+                        }
+                        // determine how long to sleep from looking at 
ticket's expiry.
+                        // We should not allow the ticket to expire, but we 
should take into consideration
+                        // MIN_TIME_BEFORE_RELOGIN. Will not sleep less than 
MIN_TIME_BEFORE_RELOGIN, unless doing so
+                        // would cause ticket expiration.
+                        if ((nextRefresh > expiry) ||
+                                ((now + MIN_TIME_BEFORE_RELOGIN) > expiry)) {
+                            // expiry is before next scheduled refresh).
+                            nextRefresh = now;
+                        } else {
+                            if (nextRefresh < (now + MIN_TIME_BEFORE_RELOGIN)) 
{
+                                // next scheduled refresh is sooner than (now 
+ MIN_TIME_BEFORE_LOGIN).
+                                Date until = new Date(nextRefresh);
+                                Date newuntil = new Date(now + 
MIN_TIME_BEFORE_RELOGIN);
+                                Object[] logPayload = {until, newuntil, 
(MIN_TIME_BEFORE_RELOGIN / 1000)};
+                                LOG.warn("TGT refresh thread time adjusted 
from : {} to : {} since "
+                                        + "the former is sooner than the 
minimum refresh interval ("
+                                        + "{} seconds) from now.", logPayload);
+                            }
+                            nextRefresh = Math.max(nextRefresh, now + 
MIN_TIME_BEFORE_RELOGIN);
+                        }
+                        nextRefreshDate = new Date(nextRefresh);
+                        if (nextRefresh > expiry) {
+                            Object[] logPayload = {nextRefreshDate, 
expiryDate};
+                            LOG.error("next refresh: {} is later than expiry 
{}."
+                                    + " This may indicate a clock skew 
problem. Check that this host and the KDC's "
+                                    + "hosts' clocks are in sync. Exiting 
refresh thread.", logPayload);
+                            return;
+                        }
+                    }
+                    if (now == nextRefresh) {
+                        LOG.info("refreshing now because expiry is before next 
scheduled refresh time.");
+                    } else if (now < nextRefresh) {
+                        Date until = new Date(nextRefresh);
+                        LOG.info("TGT refresh sleeping until: {}", 
until.toString());
+                        try {
+                            Thread.sleep(nextRefresh - now);
+                        } catch (InterruptedException ie) {
+                            LOG.warn("TGT renewal thread has been interrupted 
and will exit.");
+                            break;
+                        }
+                    }
+                    else {
+                        LOG.error("nextRefresh:{} is in the past: exiting 
refresh thread. Check"
+                                + " clock sync between this host and KDC - 
(KDC's clock is likely ahead of this host)."
+                                + " Manual intervention will be required for 
this client to successfully authenticate."
+                                + " Exiting refresh thread.", nextRefreshDate);
+                        break;
+                    }
+                    if (isUsingTicketCache) {
+                        String cmd = 
zkConfig.getProperty(ZKConfig.KINIT_COMMAND, KINIT_COMMAND_DEFAULT);
+                        String kinitArgs = "-R";
+                        int retry = 1;
+                        while (retry >= 0) {
+                            try {
+                                LOG.debug("running ticket cache refresh 
command: {} {}", cmd, kinitArgs);
+                                Shell.execCommand(cmd, kinitArgs);
+                                break;
+                            } catch (Exception e) {
+                                if (retry > 0) {
+                                    --retry;
+                                    // sleep for 10 seconds
+                                    try {
+                                        Thread.sleep(10 * 1000);
+                                    } catch (InterruptedException ie) {
+                                        LOG.error("Interrupted while renewing 
TGT, exiting Login thread");
+                                        return;
+                                    }
+                                } else {
+                                    Object[] logPayload = {cmd, kinitArgs, 
e.toString(), e};
+                                    LOG.warn("Could not renew TGT due to 
problem running shell command: '{}"
+                                            + " {}'; exception was:{}. Exiting 
refresh thread.", logPayload);
+                                    return;
+                                }
+                            }
+                        }
+                    }
+                    try {
+                        int retry = 1;
+                        while (retry >= 0) {
+                            try {
+                                reLogin();
+                                break;
+                            } catch (LoginException le) {
+                                if (retry > 0) {
+                                    --retry;
+                                    // sleep for 10 seconds.
+                                    try {
+                                        Thread.sleep(10 * 1000);
+                                    } catch (InterruptedException e) {
+                                        LOG.error("Interrupted during login 
retry after LoginException:", le);
+                                        throw le;
+                                    }
+                                } else {
+                                    LOG.error("Could not refresh TGT for 
principal: {}.", principal, le);
+                                }
+                            }
+                        }
+                    } catch (LoginException le) {
+                        LOG.error("Failed to refresh TGT: refresh thread 
exiting now.",le);
+                        break;
+                    }
+                }
+            }
+        });
+        t.setDaemon(true);
+    }
+
+    public void startThreadIfNeeded() {
+        // thread object 't' will be null if a refresh thread is not needed.
+        if (t != null) {
+            t.start();
+        }
+    }
+
+    public void shutdown() {
+        if ((t != null) && (t.isAlive())) {
+            t.interrupt();
+            try {
+                t.join();
+            } catch (InterruptedException e) {
+                LOG.warn("error while waiting for Login thread to shutdown: ", 
e);
+            }
+        }
+    }
+
+    public Subject getSubject() {
+        return subject;
+    }
+
+    public String getLoginContextName() {
+        return loginContextName;
+    }
+
+    private synchronized LoginContext login(final String loginContextName) 
throws LoginException {
+        if (loginContextName == null) {
+            throw new LoginException("loginContext name (JAAS file section 
header) was null. " +
+                    "Please check your java.security.login.auth.config (=" +
+                    System.getProperty("java.security.login.auth.config") +
+                    ") and your " + getLoginContextMessage());
+        }
+        LoginContext loginContext = new 
LoginContext(loginContextName,callbackHandler);
+        loginContext.login();
+        LOG.info("{} successfully logged in.", loginContextName);
+        return loginContext;
+    }
+
+    private String getLoginContextMessage() {
+        if (zkConfig instanceof ZKClientConfig) {
+            return ZKClientConfig.LOGIN_CONTEXT_NAME_KEY + "(=" + 
zkConfig.getProperty(
+                    ZKClientConfig.LOGIN_CONTEXT_NAME_KEY, 
ZKClientConfig.LOGIN_CONTEXT_NAME_KEY_DEFAULT) + ")";
+        } else {
+            return ZooKeeperSaslServer.LOGIN_CONTEXT_NAME_KEY + "(=" + 
System.getProperty(
+                    ZooKeeperSaslServer.LOGIN_CONTEXT_NAME_KEY, 
ZooKeeperSaslServer.DEFAULT_LOGIN_CONTEXT_NAME) + ")";
+        }
+    }
+
+    // c.f. org.apache.hadoop.security.UserGroupInformation.
+    private long getRefreshTime(KerberosTicket tgt) {
+        long start = tgt.getStartTime().getTime();
+        long expires = tgt.getEndTime().getTime();
+        LOG.info("TGT valid starting at:        {}", 
tgt.getStartTime().toString());
+        LOG.info("TGT expires:                  {}", 
tgt.getEndTime().toString());
+        long proposedRefresh = start + (long) ((expires - start) *
+                (TICKET_RENEW_WINDOW + (TICKET_RENEW_JITTER * 
rng.nextDouble())));
+        if (proposedRefresh > expires) {
+            // proposedRefresh is too far in the future: it's after ticket 
expires: simply return now.
+            return Time.currentWallTime();
+        }
+        else {
+            return proposedRefresh;
+        }
+    }
+
+    private synchronized KerberosTicket getTGT() {
+        Set<KerberosTicket> tickets = 
subject.getPrivateCredentials(KerberosTicket.class);
+        for(KerberosTicket ticket: tickets) {
+            KerberosPrincipal server = ticket.getServer();
+            if (server.getName().equals("krbtgt/" + server.getRealm() + "@" + 
server.getRealm())) {
+                LOG.debug("Client principal is \"" + 
ticket.getClient().getName() + "\".");
+                LOG.debug("Server principal is \"" + 
ticket.getServer().getName() + "\".");
+                return ticket;
+            }
+        }
+        return null;
+    }
+
+    private boolean hasSufficientTimeElapsed() {
+        long now = Time.currentElapsedTime();
+        if (now - getLastLogin() < MIN_TIME_BEFORE_RELOGIN ) {
+            LOG.warn("Not attempting to re-login since the last re-login was "
+                    + "attempted less than {} seconds before.",
+                    (MIN_TIME_BEFORE_RELOGIN / 1000));
+            return false;
+        }
+        // register most recent relogin attempt
+        setLastLogin(now);
+        return true;
+    }
+
+    /**
+     * Returns login object
+     * @return login
+     */
+    private LoginContext getLogin() {
+        return login;
+    }
+
+    /**
+     * Set the login object
+     * @param login
+     */
+    private void setLogin(LoginContext login) {
+        this.login = login;
+    }
+
+    /**
+     * Set the last login time.
+     * @param time the number of milliseconds since the beginning of time
+     */
+    private void setLastLogin(long time) {
+        lastLogin = time;
+    }
+
+    /**
+     * Get the time of the last login.
+     * @return the number of milliseconds since the beginning of time.
+     */
+    private long getLastLogin() {
+        return lastLogin;
+    }
+
+    /**
+     * Re-login a principal. This method assumes that {@link #login(String)} 
has happened already.
+     * @throws javax.security.auth.login.LoginException on a failure
+     */
+    // c.f. HADOOP-6559
+    private synchronized void reLogin()
+            throws LoginException {
+        if (!isKrbTicket) {
+            return;
+        }
+        LoginContext login = getLogin();
+        if (login  == null) {
+            throw new LoginException("login must be done first");
+        }
+        if (!hasSufficientTimeElapsed()) {
+            return;
+        }
+        LOG.info("Initiating logout for {}", principal);
+        synchronized (Login.class) {
+            //clear up the kerberos state. But the tokens are not cleared! As 
per
+            //the Java kerberos login module code, only the kerberos 
credentials
+            //are cleared
+            login.logout();
+            //login and also update the subject field of this instance to
+            //have the new credentials (pass it to the LoginContext 
constructor)
+            login = new LoginContext(loginContextName, getSubject());
+            LOG.info("Initiating re-login for {}", principal);
+            login.login();
+            setLogin(login);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/43d71c2e/zookeeper-server/src/main/java/org/apache/zookeeper/MultiResponse.java
----------------------------------------------------------------------
diff --git 
a/zookeeper-server/src/main/java/org/apache/zookeeper/MultiResponse.java 
b/zookeeper-server/src/main/java/org/apache/zookeeper/MultiResponse.java
new file mode 100644
index 0000000..5ac906a
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/MultiResponse.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zookeeper;
+
+import org.apache.jute.InputArchive;
+import org.apache.jute.OutputArchive;
+import org.apache.jute.Record;
+import org.apache.zookeeper.proto.Create2Response;
+import org.apache.zookeeper.proto.CreateResponse;
+import org.apache.zookeeper.proto.MultiHeader;
+import org.apache.zookeeper.proto.SetDataResponse;
+import org.apache.zookeeper.proto.ErrorResponse;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Handles the response from a multi request.  Such a response consists of
+ * a sequence of responses each prefixed by a MultiResponse that indicates
+ * the type of the response.  The end of the list is indicated by a MultiHeader
+ * with a negative type.  Each individual response is in the same format as
+ * with the corresponding operation in the original request list.
+ */
+public class MultiResponse implements Record, Iterable<OpResult> {
+    private List<OpResult> results = new ArrayList<OpResult>();
+
+    public void add(OpResult x) {
+        results.add(x);
+    }
+
+    @Override
+    public Iterator<OpResult> iterator() {
+        return results.iterator();
+    }
+
+    public int size() {
+        return results.size();
+    }
+
+    @Override
+    public void serialize(OutputArchive archive, String tag) throws 
IOException {
+        archive.startRecord(this, tag);
+
+        for (OpResult result : results) {
+            int err = result.getType() == ZooDefs.OpCode.error ? 
((OpResult.ErrorResult)result).getErr() : 0;
+
+            new MultiHeader(result.getType(), false, err).serialize(archive, 
tag);
+
+            switch (result.getType()) {
+                case ZooDefs.OpCode.create:
+                    new CreateResponse(((OpResult.CreateResult) 
result).getPath()).serialize(archive, tag);
+                    break;
+                case ZooDefs.OpCode.create2:
+                       OpResult.CreateResult createResult = 
(OpResult.CreateResult) result;
+                    new Create2Response(createResult.getPath(),
+                               createResult.getStat()).serialize(archive, tag);
+                    break;
+                case ZooDefs.OpCode.delete:
+                case ZooDefs.OpCode.check:
+                    break;
+                case ZooDefs.OpCode.setData:
+                    new SetDataResponse(((OpResult.SetDataResult) 
result).getStat()).serialize(archive, tag);
+                    break;
+                case ZooDefs.OpCode.error:
+                    new ErrorResponse(((OpResult.ErrorResult) 
result).getErr()).serialize(archive, tag);
+                    break;
+                default:
+                    throw new IOException("Invalid type " + result.getType() + 
" in MultiResponse");
+            }
+        }
+        new MultiHeader(-1, true, -1).serialize(archive, tag);
+        archive.endRecord(this, tag);
+    }
+
+    @Override
+    public void deserialize(InputArchive archive, String tag) throws 
IOException {
+        results = new ArrayList<OpResult>();
+
+        archive.startRecord(tag);
+        MultiHeader h = new MultiHeader();
+        h.deserialize(archive, tag);
+        while (!h.getDone()) {
+            switch (h.getType()) {
+                case ZooDefs.OpCode.create:
+                    CreateResponse cr = new CreateResponse();
+                    cr.deserialize(archive, tag);
+                    results.add(new OpResult.CreateResult(cr.getPath()));
+                    break;
+
+                case ZooDefs.OpCode.create2:
+                    Create2Response cr2 = new Create2Response();
+                    cr2.deserialize(archive, tag);
+                    results.add(new OpResult.CreateResult(cr2.getPath(), 
cr2.getStat()));
+                    break;
+
+                case ZooDefs.OpCode.delete:
+                    results.add(new OpResult.DeleteResult());
+                    break;
+
+                case ZooDefs.OpCode.setData:
+                    SetDataResponse sdr = new SetDataResponse();
+                    sdr.deserialize(archive, tag);
+                    results.add(new OpResult.SetDataResult(sdr.getStat()));
+                    break;
+
+                case ZooDefs.OpCode.check:
+                    results.add(new OpResult.CheckResult());
+                    break;
+
+                case ZooDefs.OpCode.error:
+                    //FIXME: need way to more cleanly serialize/deserialize 
exceptions
+                    ErrorResponse er = new ErrorResponse();
+                    er.deserialize(archive, tag);
+                    results.add(new OpResult.ErrorResult(er.getErr()));
+                    break;
+
+                default:
+                    throw new IOException("Invalid type " + h.getType() + " in 
MultiResponse");
+            }
+            h.deserialize(archive, tag);
+        }
+        archive.endRecord(tag);
+    }
+
+    public List<OpResult> getResultList() {
+        return results;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof MultiResponse)) return false;
+
+        MultiResponse other = (MultiResponse) o;
+
+        if (results != null) {
+            Iterator<OpResult> i = other.results.iterator();
+            for (OpResult result : results) {
+                if (i.hasNext()) {
+                    if (!result.equals(i.next())) {
+                        return false;
+                    }
+                } else {
+                    return false;
+                }
+            }
+            return !i.hasNext();
+        }
+        else return other.results == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = results.size();
+        for (OpResult result : results) {
+            hash = (hash * 35) + result.hashCode();
+        }
+        return hash;
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/43d71c2e/zookeeper-server/src/main/java/org/apache/zookeeper/MultiTransactionRecord.java
----------------------------------------------------------------------
diff --git 
a/zookeeper-server/src/main/java/org/apache/zookeeper/MultiTransactionRecord.java
 
b/zookeeper-server/src/main/java/org/apache/zookeeper/MultiTransactionRecord.java
new file mode 100644
index 0000000..336a677
--- /dev/null
+++ 
b/zookeeper-server/src/main/java/org/apache/zookeeper/MultiTransactionRecord.java
@@ -0,0 +1,163 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zookeeper;
+
+import org.apache.jute.InputArchive;
+import org.apache.jute.OutputArchive;
+import org.apache.jute.Record;
+import org.apache.zookeeper.proto.*;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Encodes a composite transaction.  In the wire format, each transaction
+ * consists of a single MultiHeader followed by the appropriate request.
+ * Each of these MultiHeaders has a type which indicates
+ * the type of the following transaction or a negative number if no more 
transactions
+ * are included.
+ */
+public class MultiTransactionRecord implements Record, Iterable<Op> {
+    private List<Op> ops = new ArrayList<Op>();
+
+    public MultiTransactionRecord() {
+    }
+
+    public MultiTransactionRecord(Iterable<Op> ops) {
+        for (Op op : ops) {
+            add(op);
+        }
+    }
+
+    @Override
+    public Iterator<Op> iterator() {
+        return ops.iterator() ;
+    }
+
+    public void add(Op op) {
+        ops.add(op);
+    }
+
+    public int size() {
+        return ops.size();
+    }
+
+    @Override
+    public void serialize(OutputArchive archive, String tag) throws 
IOException {
+        archive.startRecord(this, tag);
+        for (Op op : ops) {
+            MultiHeader h = new MultiHeader(op.getType(), false, -1);
+            h.serialize(archive, tag);
+            switch (op.getType()) {
+                case ZooDefs.OpCode.create:
+                case ZooDefs.OpCode.create2:
+                case ZooDefs.OpCode.createTTL:
+                case ZooDefs.OpCode.createContainer:
+                case ZooDefs.OpCode.delete:
+                case ZooDefs.OpCode.setData:
+                case ZooDefs.OpCode.check:
+                    op.toRequestRecord().serialize(archive, tag);
+                    break;
+                default:
+                    throw new IOException("Invalid type of op");
+            }
+        }
+        new MultiHeader(-1, true, -1).serialize(archive, tag);
+        archive.endRecord(this, tag);
+    }
+
+    @Override
+    public void deserialize(InputArchive archive, String tag) throws 
IOException {
+        archive.startRecord(tag);
+        MultiHeader h = new MultiHeader();
+        h.deserialize(archive, tag);
+
+        while (!h.getDone()) {
+            switch (h.getType()) {
+                case ZooDefs.OpCode.create:
+                case ZooDefs.OpCode.create2:
+                case ZooDefs.OpCode.createContainer:
+                    CreateRequest cr = new CreateRequest();
+                    cr.deserialize(archive, tag);
+                    add(Op.create(cr.getPath(), cr.getData(), cr.getAcl(), 
cr.getFlags()));
+                    break;
+                case ZooDefs.OpCode.createTTL:
+                    CreateTTLRequest crTtl = new CreateTTLRequest();
+                    crTtl.deserialize(archive, tag);
+                    add(Op.create(crTtl.getPath(), crTtl.getData(), 
crTtl.getAcl(), crTtl.getFlags(), crTtl.getTtl()));
+                    break;
+                case ZooDefs.OpCode.delete:
+                    DeleteRequest dr = new DeleteRequest();
+                    dr.deserialize(archive, tag);
+                    add(Op.delete(dr.getPath(), dr.getVersion()));
+                    break;
+                case ZooDefs.OpCode.setData:
+                    SetDataRequest sdr = new SetDataRequest();
+                    sdr.deserialize(archive, tag);
+                    add(Op.setData(sdr.getPath(), sdr.getData(), 
sdr.getVersion()));
+                    break;
+                case ZooDefs.OpCode.check:
+                    CheckVersionRequest cvr = new CheckVersionRequest();
+                    cvr.deserialize(archive, tag);
+                    add(Op.check(cvr.getPath(), cvr.getVersion()));
+                    break;
+                default:
+                    throw new IOException("Invalid type of op");
+            }
+            h.deserialize(archive, tag);
+        }
+        archive.endRecord(tag);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof MultiTransactionRecord)) return false;
+
+        MultiTransactionRecord that = (MultiTransactionRecord) o;
+
+        if (ops != null) {
+            Iterator<Op> other = that.ops.iterator();
+            for (Op op : ops) {
+                boolean hasMoreData = other.hasNext();
+                if (!hasMoreData) {
+                    return false;
+                }
+                Op otherOp = other.next();
+                if (!op.equals(otherOp)) {
+                    return false;
+                }
+            }
+            return !other.hasNext();
+        } else {
+            return that.ops == null;
+        }
+
+    }
+
+    @Override
+    public int hashCode() {
+        int h = 1023;
+        for (Op op : ops) {
+            h = h * 25 + op.hashCode();
+        }
+        return h;
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/43d71c2e/zookeeper-server/src/main/java/org/apache/zookeeper/Op.java
----------------------------------------------------------------------
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/Op.java 
b/zookeeper-server/src/main/java/org/apache/zookeeper/Op.java
new file mode 100644
index 0000000..c73cc79
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/Op.java
@@ -0,0 +1,452 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zookeeper;
+
+import org.apache.jute.Record;
+import org.apache.zookeeper.common.PathUtils;
+import org.apache.zookeeper.data.ACL;
+import org.apache.zookeeper.proto.CheckVersionRequest;
+import org.apache.zookeeper.proto.CreateRequest;
+import org.apache.zookeeper.proto.CreateTTLRequest;
+import org.apache.zookeeper.proto.DeleteRequest;
+import org.apache.zookeeper.proto.SetDataRequest;
+import org.apache.zookeeper.server.EphemeralType;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Represents a single operation in a multi-operation transaction.  Each 
operation can be a create, update
+ * or delete or can just be a version check.
+ *
+ * Sub-classes of Op each represent each detailed type but should not normally 
be referenced except via
+ * the provided factory methods.
+ *
+ * @see ZooKeeper#create(String, byte[], java.util.List, CreateMode)
+ * @see ZooKeeper#create(String, byte[], java.util.List, CreateMode, 
org.apache.zookeeper.AsyncCallback.StringCallback, Object)
+ * @see ZooKeeper#delete(String, int)
+ * @see ZooKeeper#setData(String, byte[], int)
+ */
+public abstract class Op {
+    private int type;
+    private String path;
+
+    // prevent untyped construction
+    private Op(int type, String path) {
+        this.type = type;
+        this.path = path;
+    }
+
+    /**
+     * Constructs a create operation.  Arguments are as for the ZooKeeper 
method of the same name.
+     * @see ZooKeeper#create(String, byte[], java.util.List, CreateMode)
+     * @see CreateMode#fromFlag(int)
+     *
+     * @param path
+     *                the path for the node
+     * @param data
+     *                the initial data for the node
+     * @param acl
+     *                the acl for the node
+     * @param flags
+     *                specifying whether the node to be created is ephemeral
+     *                and/or sequential but using the integer encoding.
+     */
+    public static Op create(String path, byte[] data, List<ACL> acl, int 
flags) {
+        return new Create(path, data, acl, flags);
+    }
+
+    /**
+     * Constructs a create operation.  Arguments are as for the ZooKeeper 
method of the same name
+     * but adding an optional ttl
+     * @see ZooKeeper#create(String, byte[], java.util.List, CreateMode)
+     * @see CreateMode#fromFlag(int)
+     *
+     * @param path
+     *                the path for the node
+     * @param data
+     *                the initial data for the node
+     * @param acl
+     *                the acl for the node
+     * @param flags
+     *                specifying whether the node to be created is ephemeral
+     *                and/or sequential but using the integer encoding.
+     * @param ttl
+     *                optional ttl or 0 (flags must imply a TTL creation mode)
+     */
+    public static Op create(String path, byte[] data, List<ACL> acl, int 
flags, long ttl) {
+        CreateMode createMode = CreateMode.fromFlag(flags, 
CreateMode.PERSISTENT);
+        if (createMode.isTTL()) {
+            return new CreateTTL(path, data, acl, createMode, ttl);
+        }
+        return new Create(path, data, acl, flags);
+    }
+
+    /**
+     * Constructs a create operation.  Arguments are as for the ZooKeeper 
method of the same name.
+     * @see ZooKeeper#create(String, byte[], java.util.List, CreateMode)
+     *
+     * @param path
+     *                the path for the node
+     * @param data
+     *                the initial data for the node
+     * @param acl
+     *                the acl for the node
+     * @param createMode
+     *                specifying whether the node to be created is ephemeral
+     *                and/or sequential
+     */
+    public static Op create(String path, byte[] data, List<ACL> acl, 
CreateMode createMode) {
+        return new Create(path, data, acl, createMode);
+    }
+
+    /**
+     * Constructs a create operation.  Arguments are as for the ZooKeeper 
method of the same name
+     * but adding an optional ttl
+     * @see ZooKeeper#create(String, byte[], java.util.List, CreateMode)
+     *
+     * @param path
+     *                the path for the node
+     * @param data
+     *                the initial data for the node
+     * @param acl
+     *                the acl for the node
+     * @param createMode
+     *                specifying whether the node to be created is ephemeral
+     *                and/or sequential
+     * @param ttl
+     *                optional ttl or 0 (createMode must imply a TTL)
+     */
+    public static Op create(String path, byte[] data, List<ACL> acl, 
CreateMode createMode, long ttl) {
+        if (createMode.isTTL()) {
+            return new CreateTTL(path, data, acl, createMode, ttl);
+        }
+        return new Create(path, data, acl, createMode);
+    }
+
+    /**
+     * Constructs a delete operation.  Arguments are as for the ZooKeeper 
method of the same name.
+     * @see ZooKeeper#delete(String, int)
+     *
+     * @param path
+     *                the path of the node to be deleted.
+     * @param version
+     *                the expected node version.
+     */
+    public static Op delete(String path, int version) {
+        return new Delete(path, version);
+    }
+
+    /**
+     * Constructs an update operation.  Arguments are as for the ZooKeeper 
method of the same name.
+     * @see ZooKeeper#setData(String, byte[], int)
+     *
+     * @param path
+     *                the path of the node
+     * @param data
+     *                the data to set
+     * @param version
+     *                the expected matching version
+     */
+    public static Op setData(String path, byte[] data, int version) {
+        return new SetData(path, data, version);
+    }
+
+
+    /**
+     * Constructs an version check operation.  Arguments are as for the 
ZooKeeper.setData method except that
+     * no data is provided since no update is intended.  The purpose for this 
is to allow read-modify-write
+     * operations that apply to multiple znodes, but where some of the znodes 
are involved only in the read,
+     * not the write.  A similar effect could be achieved by writing the same 
data back, but that leads to
+     * way more version updates than are necessary and more writing in general.
+     *
+     * @param path
+     *                the path of the node
+     * @param version
+     *                the expected matching version
+     */
+    public static Op check(String path, int version) {
+        return new Check(path, version);
+    }
+
+    /**
+     * Gets the integer type code for an Op.  This code should be as from 
ZooDefs.OpCode
+     * @see ZooDefs.OpCode
+     * @return  The type code.
+     */
+    public int getType() {
+        return type;
+    }
+
+    /**
+     * Gets the path for an Op.
+     * @return  The path.
+     */
+    public String getPath() {
+        return path;
+    }
+
+    /**
+     * Encodes an op for wire transmission.
+     * @return An appropriate Record structure.
+     */
+    public abstract Record toRequestRecord() ;
+    
+    /**
+     * Reconstructs the transaction with the chroot prefix.
+     * @return transaction with chroot.
+     */
+    abstract Op withChroot(String addRootPrefix);
+
+    /**
+     * Performs client path validations.
+     * 
+     * @throws IllegalArgumentException
+     *             if an invalid path is specified
+     * @throws KeeperException.BadArgumentsException
+     *             if an invalid create mode flag is specified
+     */
+    void validate() throws KeeperException {
+        PathUtils.validatePath(path);
+    }
+
+    //////////////////
+    // these internal classes are public, but should not generally be 
referenced.
+    //
+    public static class Create extends Op {
+        protected byte[] data;
+        protected List<ACL> acl;
+        protected int flags;
+
+        private Create(String path, byte[] data, List<ACL> acl, int flags) {
+            super(getOpcode(CreateMode.fromFlag(flags, 
CreateMode.PERSISTENT)), path);
+            this.data = data;
+            this.acl = acl;
+            this.flags = flags;
+        }
+
+        private static int getOpcode(CreateMode createMode) {
+            if (createMode.isTTL()) {
+                return ZooDefs.OpCode.createTTL;
+            }
+            return createMode.isContainer() ? ZooDefs.OpCode.createContainer : 
ZooDefs.OpCode.create;
+        }
+
+        private Create(String path, byte[] data, List<ACL> acl, CreateMode 
createMode) {
+            super(getOpcode(createMode), path);
+            this.data = data;
+            this.acl = acl;
+            this.flags = createMode.toFlag();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof Create)) return false;
+
+            Create op = (Create) o;
+
+            boolean aclEquals = true;
+            Iterator<ACL> i = op.acl.iterator();
+            for (ACL acl : op.acl) {
+                boolean hasMoreData = i.hasNext();
+                if (!hasMoreData) {
+                    aclEquals = false;
+                    break;
+                }
+                ACL otherAcl = i.next();
+                if (!acl.equals(otherAcl)) {
+                    aclEquals = false;
+                    break;
+                }
+            }
+            return !i.hasNext() && getType() == op.getType() && 
Arrays.equals(data, op.data) && flags == op.flags && aclEquals;
+        }
+
+        @Override
+        public int hashCode() {
+            return getType() + getPath().hashCode() + Arrays.hashCode(data);
+        }
+
+        @Override
+        public Record toRequestRecord() {
+            return new CreateRequest(getPath(), data, acl, flags);
+        }
+
+        @Override
+        Op withChroot(String path) {
+            return new Create(path, data, acl, flags);
+        }
+
+        @Override
+        void validate() throws KeeperException {
+            CreateMode createMode = CreateMode.fromFlag(flags);
+            PathUtils.validatePath(getPath(), createMode.isSequential());
+            EphemeralType.validateTTL(createMode, -1);
+        }
+    }
+
+    public static class CreateTTL extends Create {
+        private final long ttl;
+
+        private CreateTTL(String path, byte[] data, List<ACL> acl, int flags, 
long ttl) {
+            super(path, data, acl, flags);
+            this.ttl = ttl;
+        }
+
+        private CreateTTL(String path, byte[] data, List<ACL> acl, CreateMode 
createMode, long ttl) {
+            super(path, data, acl, createMode);
+            this.ttl = ttl;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            return super.equals(o) && (o instanceof CreateTTL) && (ttl == 
((CreateTTL)o).ttl);
+        }
+
+        @Override
+        public int hashCode() {
+            return super.hashCode() + (int)(ttl ^ (ttl >>> 32));
+        }
+
+        @Override
+        public Record toRequestRecord() {
+            return new CreateTTLRequest(getPath(), data, acl, flags, ttl);
+        }
+
+        @Override
+        Op withChroot(String path) {
+            return new CreateTTL(path, data, acl, flags, ttl);
+        }
+
+        @Override
+        void validate() throws KeeperException {
+            CreateMode createMode = CreateMode.fromFlag(flags);
+            PathUtils.validatePath(getPath(), createMode.isSequential());
+            EphemeralType.validateTTL(createMode, ttl);
+        }
+    }
+
+    public static class Delete extends Op {
+        private int version;
+
+        private Delete(String path, int version) {
+            super(ZooDefs.OpCode.delete, path);
+            this.version = version;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof Delete)) return false;
+
+            Delete op = (Delete) o;
+
+            return getType() == op.getType() && version == op.version 
+                   && getPath().equals(op.getPath());
+        }
+
+        @Override
+        public int hashCode() {
+            return getType() + getPath().hashCode() + version;
+        }
+
+        @Override
+        public Record toRequestRecord() {
+            return new DeleteRequest(getPath(), version);
+        }
+
+        @Override
+        Op withChroot(String path) {
+            return new Delete(path, version);
+        }
+    }
+
+    public static class SetData extends Op {
+        private byte[] data;
+        private int version;
+
+        private SetData(String path, byte[] data, int version) {
+            super(ZooDefs.OpCode.setData, path);
+            this.data = data;
+            this.version = version;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof SetData)) return false;
+
+            SetData op = (SetData) o;
+
+            return getType() == op.getType() && version == op.version 
+                   && getPath().equals(op.getPath()) && Arrays.equals(data, 
op.data);
+        }
+
+        @Override
+        public int hashCode() {
+            return getType() + getPath().hashCode() + Arrays.hashCode(data) + 
version;
+        }
+
+        @Override
+        public Record toRequestRecord() {
+            return new SetDataRequest(getPath(), data, version);
+        }
+
+        @Override
+        Op withChroot(String path) {
+            return new SetData(path, data, version);
+        }
+    }
+
+    public static class Check extends Op {
+        private int version;
+
+        private Check(String path, int version) {
+            super(ZooDefs.OpCode.check, path);
+            this.version = version;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof Check)) return false;
+
+            Check op = (Check) o;
+
+            return getType() == op.getType() && getPath().equals(op.getPath()) 
&& version == op.version;
+        }
+
+        @Override
+        public int hashCode() {
+            return getType() + getPath().hashCode() + version;
+        }
+
+        @Override
+        public Record toRequestRecord() {
+            return new CheckVersionRequest(getPath(), version);
+        }
+
+        @Override
+        Op withChroot(String path) {
+            return new Check(path, version);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/43d71c2e/zookeeper-server/src/main/java/org/apache/zookeeper/OpResult.java
----------------------------------------------------------------------
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/OpResult.java 
b/zookeeper-server/src/main/java/org/apache/zookeeper/OpResult.java
new file mode 100644
index 0000000..d294b8f
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/OpResult.java
@@ -0,0 +1,205 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zookeeper;
+
+
+import org.apache.zookeeper.data.Stat;
+
+/**
+ * Encodes the result of a single part of a multiple operation commit.
+ */
+public abstract class OpResult {
+    private int type;
+
+    private OpResult(int type) {
+        this.type = type;
+    }
+
+    /**
+     * Encodes the return type as from ZooDefs.OpCode.  Can be used
+     * to dispatch to the correct cast needed for getting the desired
+     * additional result data.
+     * @see ZooDefs.OpCode
+     * @return an integer identifying what kind of operation this result came 
from.
+     */
+    public int getType() {
+        return type;
+    }
+
+    /**
+     * A result from a create operation.  This kind of result allows the
+     * path to be retrieved since the create might have been a sequential
+     * create.
+     */
+    public static class CreateResult extends OpResult {
+        private String path;
+        private Stat stat;
+
+        public CreateResult(String path) {
+               this(ZooDefs.OpCode.create, path, null);
+        }
+
+        public CreateResult(String path, Stat stat) {
+            this(ZooDefs.OpCode.create2, path, stat);
+        }
+
+        private CreateResult(int opcode, String path, Stat stat) {
+               super(opcode);
+            this.path = path;
+            this.stat = stat;
+        }
+
+        public String getPath() {
+            return path;
+        }
+
+        public Stat getStat() {
+            return stat;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof CreateResult)) return false;
+
+            CreateResult other = (CreateResult) o;
+
+            boolean statsAreEqual = (stat == null && other.stat == null ||
+                                                                       (stat 
!= null && other.stat != null &&
+                                                                  
stat.getMzxid() == other.stat.getMzxid()));
+            return getType() == other.getType() &&
+                   path.equals(other.getPath()) && statsAreEqual;
+        }
+
+        @Override
+        public int hashCode() {
+            return (int) (getType() * 35 + path.hashCode() +
+                    (stat == null ? 0 : stat.getMzxid()));
+        }
+    }
+
+    /**
+     * A result from a delete operation.  No special values are available.
+     */
+    public static class DeleteResult extends OpResult {
+        public DeleteResult() {
+            super(ZooDefs.OpCode.delete);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof DeleteResult)) return false;
+
+            DeleteResult opResult = (DeleteResult) o;
+            return getType() == opResult.getType();
+        }
+
+        @Override
+        public int hashCode() {
+            return getType();
+        }
+    }
+
+    /**
+     * A result from a setData operation.  This kind of result provides access
+     * to the Stat structure from the update.
+     */
+    public static class SetDataResult extends OpResult {
+        private Stat stat;
+
+        public SetDataResult(Stat stat) {
+            super(ZooDefs.OpCode.setData);
+            this.stat = stat;
+        }
+
+        public Stat getStat() {
+            return stat;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof SetDataResult)) return false;
+
+            SetDataResult other = (SetDataResult) o;
+            return getType() == other.getType() && stat.getMzxid() == 
other.stat.getMzxid();
+        }
+
+        @Override
+        public int hashCode() {
+            return (int) (getType() * 35 + stat.getMzxid());
+        }
+    }
+
+    /**
+     * A result from a version check operation.  No special values are 
available.
+     */
+    public static class CheckResult extends OpResult {
+        public CheckResult() {
+            super(ZooDefs.OpCode.check);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof CheckResult)) return false;
+
+            CheckResult other = (CheckResult) o;
+            return getType() == other.getType();
+        }
+
+        @Override
+        public int hashCode() {
+            return getType();
+        }
+    }
+
+    /**
+     * An error result from any kind of operation.  The point of error results
+     * is that they contain an error code which helps understand what happened.
+     * @see KeeperException.Code
+     *
+     */
+    public static class ErrorResult extends OpResult {
+        private int err;
+
+        public ErrorResult(int err) {
+            super(ZooDefs.OpCode.error);
+            this.err = err;
+        }
+
+        public int getErr() {
+            return err;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof ErrorResult)) return false;
+
+            ErrorResult other = (ErrorResult) o;
+            return getType() == other.getType() && err == other.getErr();
+        }
+
+        @Override
+        public int hashCode() {
+            return getType() * 35 + err;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/43d71c2e/zookeeper-server/src/main/java/org/apache/zookeeper/Quotas.java
----------------------------------------------------------------------
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/Quotas.java 
b/zookeeper-server/src/main/java/org/apache/zookeeper/Quotas.java
new file mode 100644
index 0000000..b82e339
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/Quotas.java
@@ -0,0 +1,68 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zookeeper;
+
+/**
+ * this class manages quotas
+ * and has many other utils
+ * for quota
+ */
+public class Quotas {
+
+    /** the zookeeper nodes that acts as the management and status node **/
+    public static final String procZookeeper = "/zookeeper";
+
+    /** the zookeeper quota node that acts as the quota
+     * management node for zookeeper */
+    public static final String quotaZookeeper = "/zookeeper/quota";
+
+    /**
+     * the limit node that has the limit of
+     * a subtree
+     */
+    public static final String limitNode = "zookeeper_limits";
+
+    /**
+     * the stat node that monitors the limit of
+     * a subtree.
+     */
+    public static final String statNode = "zookeeper_stats";
+
+    /**
+     * return the quota path associated with this
+     * prefix
+     * @param path the actual path in zookeeper.
+     * @return the limit quota path
+     */
+    public static String quotaPath(String path) {
+        return quotaZookeeper + path +
+        "/" + limitNode;
+    }
+
+    /**
+     * return the stat quota path associated with this
+     * prefix.
+     * @param path the actual path in zookeeper
+     * @return the stat quota path
+     */
+    public static String statPath(String path) {
+        return quotaZookeeper + path + "/" +
+        statNode;
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/43d71c2e/zookeeper-server/src/main/java/org/apache/zookeeper/SaslClientCallbackHandler.java
----------------------------------------------------------------------
diff --git 
a/zookeeper-server/src/main/java/org/apache/zookeeper/SaslClientCallbackHandler.java
 
b/zookeeper-server/src/main/java/org/apache/zookeeper/SaslClientCallbackHandler.java
new file mode 100644
index 0000000..d6f5549
--- /dev/null
+++ 
b/zookeeper-server/src/main/java/org/apache/zookeeper/SaslClientCallbackHandler.java
@@ -0,0 +1,104 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.zookeeper;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.sasl.AuthorizeCallback;
+import javax.security.sasl.RealmCallback;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is used by the SASL mechanisms to get further information to complete
+ * the authentication. For example, a SASL mechanism might use this callback
+ * handler to do verification operation. The CallbackHandler interface here
+ * refers to javax.security.auth.callback.CallbackHandler. It should not be
+ * confused with ZooKeeper packet callbacks like
+ * org.apache.zookeeper.server.auth.SaslServerCallbackHandler.
+ */
+public class SaslClientCallbackHandler implements CallbackHandler {
+    private String password = null;
+    private static final Logger LOG = 
LoggerFactory.getLogger(SaslClientCallbackHandler.class);
+    private final String entity;
+    public SaslClientCallbackHandler(String password, String client) {
+        this.password = password;
+        this.entity = client;
+    }
+
+    public void handle(Callback[] callbacks) throws 
UnsupportedCallbackException {
+        for (Callback callback : callbacks) {
+            if (callback instanceof NameCallback) {
+                NameCallback nc = (NameCallback) callback;
+                nc.setName(nc.getDefaultName());
+            }
+            else {
+                if (callback instanceof PasswordCallback) {
+                    PasswordCallback pc = (PasswordCallback)callback;
+                    if (password != null) {
+                        pc.setPassword(this.password.toCharArray());
+                    } else {
+                        LOG.warn("Could not login: the {} is being asked for a 
password, but the ZooKeeper {}" +
+                          " code does not currently support obtaining a 
password from the user." +
+                          " Make sure that the {} is configured to use a 
ticket cache (using" +
+                          " the JAAS configuration setting 
'useTicketCache=true)' and restart the {}. If" +
+                          " you still get this message after that, the TGT in 
the ticket cache has expired and must" +
+                          " be manually refreshed. To do so, first determine 
if you are using a password or a" +
+                          " keytab. If the former, run kinit in a Unix shell 
in the environment of the user who" +
+                          " is running this Zookeeper {} using the command" +
+                          " 'kinit <princ>' (where <princ> is the name of the 
{}'s Kerberos principal)." +
+                          " If the latter, do" +
+                          " 'kinit -k -t <keytab> <princ>' (where <princ> is 
the name of the Kerberos principal, and" +
+                          " <keytab> is the location of the keytab file). 
After manually refreshing your cache," +
+                          " restart this {}. If you continue to see this 
message after manually refreshing" +
+                          " your cache, ensure that your KDC host's clock is 
in sync with this host's clock.",
+                          new Object[]{entity, entity, entity, entity, entity, 
entity, entity});
+                    }
+                }
+                else {
+                    if (callback instanceof RealmCallback) {
+                        RealmCallback rc = (RealmCallback) callback;
+                        rc.setText(rc.getDefaultText());
+                    }
+                    else {
+                        if (callback instanceof AuthorizeCallback) {
+                            AuthorizeCallback ac = (AuthorizeCallback) 
callback;
+                            String authid = ac.getAuthenticationID();
+                            String authzid = ac.getAuthorizationID();
+                            if (authid.equals(authzid)) {
+                                ac.setAuthorized(true);
+                            } else {
+                                ac.setAuthorized(false);
+                            }
+                            if (ac.isAuthorized()) {
+                                ac.setAuthorizedID(authzid);
+                            }
+                        }
+                        else {
+                            throw new UnsupportedCallbackException(callback, 
"Unrecognized SASL " + entity + "Callback");
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/43d71c2e/zookeeper-server/src/main/java/org/apache/zookeeper/ServerAdminClient.java
----------------------------------------------------------------------
diff --git 
a/zookeeper-server/src/main/java/org/apache/zookeeper/ServerAdminClient.java 
b/zookeeper-server/src/main/java/org/apache/zookeeper/ServerAdminClient.java
new file mode 100644
index 0000000..5efa53e
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/ServerAdminClient.java
@@ -0,0 +1,280 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zookeeper;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+
+import org.apache.yetus.audience.InterfaceAudience;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@InterfaceAudience.Public
+public class ServerAdminClient {
+    private static final Logger LOG = 
LoggerFactory.getLogger(ServerAdminClient.class);
+
+    public static void ruok(String host, int port) {
+        Socket s = null;
+        try {
+            byte[] reqBytes = new byte[4];
+            ByteBuffer req = ByteBuffer.wrap(reqBytes);
+            req.putInt(ByteBuffer.wrap("ruok".getBytes()).getInt());
+            s = new Socket();
+            s.setSoLinger(false, 10);
+            s.setSoTimeout(20000);
+            s.connect(new InetSocketAddress(host, port));
+
+            InputStream is = s.getInputStream();
+            OutputStream os = s.getOutputStream();
+
+            os.write(reqBytes);
+
+            byte[] resBytes = new byte[4];
+
+            int rc = is.read(resBytes);
+            String retv = new String(resBytes);
+            System.out.println("rc=" + rc + " retv=" + retv);
+        } catch (IOException e) {
+            LOG.warn("Unexpected exception", e);
+        } finally {
+            if (s != null) {
+                try {
+                    s.close();
+                } catch (IOException e) {
+                    LOG.warn("Unexpected exception", e);
+                }
+            }
+        }
+    }
+
+    public static void dump(String host, int port) {
+        Socket s = null;
+        try {
+            byte[] reqBytes = new byte[4];
+            ByteBuffer req = ByteBuffer.wrap(reqBytes);
+            req.putInt(ByteBuffer.wrap("dump".getBytes()).getInt());
+            s = new Socket();
+            s.setSoLinger(false, 10);
+            s.setSoTimeout(20000);
+            s.connect(new InetSocketAddress(host, port));
+
+            InputStream is = s.getInputStream();
+            OutputStream os = s.getOutputStream();
+
+            os.write(reqBytes);
+
+            byte[] resBytes = new byte[1024];
+
+            int rc = is.read(resBytes);
+            String retv = new String(resBytes);
+            System.out.println("rc=" + rc + " retv=" + retv);
+        } catch (IOException e) {
+            LOG.warn("Unexpected exception", e);
+        } finally {
+            if (s != null) {
+                try {
+                    s.close();
+                } catch (IOException e) {
+                    LOG.warn("Unexpected exception", e);
+                }
+            }
+        }
+    }
+
+    public static void stat(String host, int port) {
+        Socket s = null;
+        try {
+            byte[] reqBytes = new byte[4];
+            ByteBuffer req = ByteBuffer.wrap(reqBytes);
+            req.putInt(ByteBuffer.wrap("stat".getBytes()).getInt());
+            s = new Socket();
+            s.setSoLinger(false, 10);
+            s.setSoTimeout(20000);
+            s.connect(new InetSocketAddress(host, port));
+
+            InputStream is = s.getInputStream();
+            OutputStream os = s.getOutputStream();
+
+            os.write(reqBytes);
+
+            byte[] resBytes = new byte[1024];
+
+            int rc = is.read(resBytes);
+            String retv = new String(resBytes);
+            System.out.println("rc=" + rc + " retv=" + retv);
+        } catch (IOException e) {
+            LOG.warn("Unexpected exception", e);
+        } finally {
+            if (s != null) {
+                try {
+                    s.close();
+                } catch (IOException e) {
+                    LOG.warn("Unexpected exception", e);
+                }
+            }
+        }
+    }
+
+    public static void kill(String host, int port) {
+        Socket s = null;
+        try {
+            byte[] reqBytes = new byte[4];
+            ByteBuffer req = ByteBuffer.wrap(reqBytes);
+            req.putInt(ByteBuffer.wrap("kill".getBytes()).getInt());
+            s = new Socket();
+            s.setSoLinger(false, 10);
+            s.setSoTimeout(20000);
+            s.connect(new InetSocketAddress(host, port));
+
+            InputStream is = s.getInputStream();
+            OutputStream os = s.getOutputStream();
+
+            os.write(reqBytes);
+            byte[] resBytes = new byte[4];
+
+            int rc = is.read(resBytes);
+            String retv = new String(resBytes);
+            System.out.println("rc=" + rc + " retv=" + retv);
+        } catch (IOException e) {
+            LOG.warn("Unexpected exception", e);
+        } finally {
+            if (s != null) {
+                try {
+                    s.close();
+                } catch (IOException e) {
+                    LOG.warn("Unexpected exception", e);
+                }
+            }
+        }
+    }
+
+    public static void setTraceMask(String host, int port, String 
traceMaskStr) {
+        Socket s = null;
+        try {
+            byte[] reqBytes = new byte[12];
+            ByteBuffer req = ByteBuffer.wrap(reqBytes);
+            long traceMask = Long.parseLong(traceMaskStr, 8);
+            req.putInt(ByteBuffer.wrap("stmk".getBytes()).getInt());
+            req.putLong(traceMask);
+
+            s = new Socket();
+            s.setSoLinger(false, 10);
+            s.setSoTimeout(20000);
+            s.connect(new InetSocketAddress(host, port));
+
+            InputStream is = s.getInputStream();
+            OutputStream os = s.getOutputStream();
+
+            os.write(reqBytes);
+
+            byte[] resBytes = new byte[8];
+
+            int rc = is.read(resBytes);
+            ByteBuffer res = ByteBuffer.wrap(resBytes);
+            long retv = res.getLong();
+            System.out.println("rc=" + rc + " retv=0"
+                    + Long.toOctalString(retv) + " masks=0"
+                    + Long.toOctalString(traceMask));
+            assert (retv == traceMask);
+        } catch (IOException e) {
+            LOG.warn("Unexpected exception", e);
+        } finally {
+            if (s != null) {
+                try {
+                    s.close();
+                } catch (IOException e) {
+                    LOG.warn("Unexpected exception", e);
+                }
+            }
+        }
+    }
+
+    public static void getTraceMask(String host, int port) {
+        Socket s = null;
+        try {
+            byte[] reqBytes = new byte[12];
+            ByteBuffer req = ByteBuffer.wrap(reqBytes);
+            req.putInt(ByteBuffer.wrap("gtmk".getBytes()).getInt());
+
+            s = new Socket();
+            s.setSoLinger(false, 10);
+            s.setSoTimeout(20000);
+            s.connect(new InetSocketAddress(host, port));
+
+            InputStream is = s.getInputStream();
+            OutputStream os = s.getOutputStream();
+
+            os.write(reqBytes);
+
+            byte[] resBytes = new byte[8];
+
+            int rc = is.read(resBytes);
+            ByteBuffer res = ByteBuffer.wrap(resBytes);
+            long retv = res.getLong();
+            System.out.println("rc=" + rc + " retv=0"
+                    + Long.toOctalString(retv));
+        } catch (IOException e) {
+            LOG.warn("Unexpected exception", e);
+        } finally {
+            if (s != null) {
+                try {
+                    s.close();
+                } catch (IOException e) {
+                    LOG.warn("Unexpected exception", e);
+                }
+            }
+        }
+    }
+
+    private static void usage() {
+        System.out
+                .println("usage: java [-cp CLASSPATH] 
org.apache.zookeeper.ServerAdminClient "
+                        + "host port op 
(ruok|stat|dump|kill|gettracemask|settracemask) [arguments]");
+
+    }
+
+    public static void main(String[] args) {
+        if (args.length < 3) {
+            usage();
+            return;
+        }
+        String host = args[0];
+        int port = Integer.parseInt(args[1]);
+        String op = args[2];
+        if (op.equalsIgnoreCase("gettracemask")) {
+            getTraceMask(host, port);
+        } else if (op.equalsIgnoreCase("settracemask")) {
+            setTraceMask(host, port, args[3]);
+        } else if (op.equalsIgnoreCase("ruok")) {
+            ruok(host, port);
+        } else if (op.equalsIgnoreCase("kill")) {
+            kill(host, port);
+        } else if (op.equalsIgnoreCase("stat")) {
+            stat(host, port);
+        } else if (op.equalsIgnoreCase("dump")) {
+            dump(host, port);
+        } else {
+            System.out.println("Unrecognized op: " + op);
+        }
+    }
+}

Reply via email to