Java tutorial
/** * Copyright 2013 Boundary, Inc. * * Licensed 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 com.boundary.zoocreeper; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; import com.google.common.base.Preconditions; import com.google.common.io.Closeables; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.KeeperException.NoNodeException; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.data.ACL; import org.apache.zookeeper.data.Stat; import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.CmdLineParser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.Collections; import java.util.List; import java.util.zip.GZIPOutputStream; /** * Backup command. */ public class Backup { private static final Logger LOGGER = LoggerFactory.getLogger(Backup.class); private static final JsonFactory JSON_FACTORY = new JsonFactory(); public static final String FIELD_AVERSION = "aversion"; public static final String FIELD_CTIME = "ctime"; public static final String FIELD_CVERSION = "cversion"; public static final String FIELD_CZXID = "czxid"; public static final String FIELD_EPHEMERAL_OWNER = "ephemeralOwner"; public static final String FIELD_MTIME = "mtime"; public static final String FIELD_MZXID = "mzxid"; public static final String FIELD_PZXID = "pzxid"; public static final String FIELD_VERSION = "version"; public static final String FIELD_DATA = "data"; public static final String FIELD_ACLS = "acls"; public static final String FIELD_ACL_ID = "id"; public static final String FIELD_ACL_SCHEME = "scheme"; public static final String FIELD_ACL_PERMS = "perms"; private final BackupOptions options; public Backup(BackupOptions options) { this.options = Preconditions.checkNotNull(options); } public void backup(OutputStream os) throws InterruptedException, IOException, KeeperException { JsonGenerator jgen = null; ZooKeeper zk = null; try { zk = options.createZooKeeper(LOGGER); jgen = JSON_FACTORY.createGenerator(os); if (options.prettyPrint) { jgen.setPrettyPrinter(new DefaultPrettyPrinter()); } jgen.writeStartObject(); if (zk.exists(options.rootPath, false) == null) { LOGGER.warn("Root path not found: {}", options.rootPath); } else { doBackup(zk, jgen, options.rootPath); } jgen.writeEndObject(); } finally { if (jgen != null) { jgen.close(); } if (zk != null) { zk.close(); } } } private static String createFullPath(String path, String childPath) { final String fullChildPath; if (path.endsWith("/")) { fullChildPath = path + childPath; } else { fullChildPath = path + '/' + childPath; } return fullChildPath; } private static <T> List<T> nullToEmpty(List<T> original) { return (original != null) ? original : Collections.<T>emptyList(); } private void doBackup(ZooKeeper zk, JsonGenerator jgen, String path) throws KeeperException, InterruptedException, IOException { try { final Stat stat = new Stat(); List<ACL> acls = nullToEmpty(zk.getACL(path, stat)); if (stat.getEphemeralOwner() != 0 && !options.backupEphemeral) { LOGGER.debug("Skipping ephemeral node: {}", path); return; } final Stat dataStat = new Stat(); byte[] data = zk.getData(path, false, dataStat); for (int i = 0; stat.compareTo(dataStat) != 0 && i < options.numRetries; i++) { LOGGER.warn("Retrying getACL / getData to read consistent state"); acls = zk.getACL(path, stat); data = zk.getData(path, false, dataStat); } if (stat.compareTo(dataStat) != 0) { throw new IllegalStateException("Unable to read consistent data for znode: " + path); } LOGGER.debug("Backing up node: {}", path); dumpNode(jgen, path, stat, acls, data); final List<String> childPaths = nullToEmpty(zk.getChildren(path, false, null)); Collections.sort(childPaths); for (String childPath : childPaths) { final String fullChildPath = createFullPath(path, childPath); if (!this.options.isPathExcluded(LOGGER, fullChildPath)) { if (this.options.isPathIncluded(LOGGER, fullChildPath)) { doBackup(zk, jgen, fullChildPath); } } } } catch (NoNodeException e) { LOGGER.warn("Node disappeared during backup: {}", path); } } private void dumpNode(JsonGenerator jgen, String path, Stat stat, List<ACL> acls, byte[] data) throws IOException { jgen.writeObjectFieldStart(path); // The number of changes to the ACL of this znode. jgen.writeNumberField(FIELD_AVERSION, stat.getAversion()); // The time in milliseconds from epoch when this znode was created. jgen.writeNumberField(FIELD_CTIME, stat.getCtime()); // The number of changes to the children of this znode. jgen.writeNumberField(FIELD_CVERSION, stat.getCversion()); // The zxid of the change that caused this znode to be created. jgen.writeNumberField(FIELD_CZXID, stat.getCzxid()); // The length of the data field of this znode. // jgen.writeNumberField("dataLength", stat.getDataLength()); // The session id of the owner of this znode if the znode is an ephemeral node. If it is not an ephemeral node, // it will be zero. jgen.writeNumberField(FIELD_EPHEMERAL_OWNER, stat.getEphemeralOwner()); // The time in milliseconds from epoch when this znode was last modified. jgen.writeNumberField(FIELD_MTIME, stat.getMtime()); // The zxid of the change that last modified this znode. jgen.writeNumberField(FIELD_MZXID, stat.getMzxid()); // The number of children of this znode. jgen.writeNumberField("numChildren", stat.getNumChildren()); // last modified children? jgen.writeNumberField(FIELD_PZXID, stat.getPzxid()); // The number of changes to the data of this znode. jgen.writeNumberField(FIELD_VERSION, stat.getVersion()); if (data != null) { jgen.writeBinaryField(FIELD_DATA, data); } else { jgen.writeNullField(FIELD_DATA); } jgen.writeArrayFieldStart(FIELD_ACLS); for (ACL acl : acls) { jgen.writeStartObject(); jgen.writeStringField(FIELD_ACL_ID, acl.getId().getId()); jgen.writeStringField(FIELD_ACL_SCHEME, acl.getId().getScheme()); jgen.writeNumberField(FIELD_ACL_PERMS, acl.getPerms()); jgen.writeEndObject(); } jgen.writeEndArray(); jgen.writeEndObject(); } private static void usage(CmdLineParser parser, int exitCode) { System.err.println(Backup.class.getName() + " [options...] arguments..."); parser.printUsage(System.err); System.exit(exitCode); } public static void main(String[] args) throws IOException, InterruptedException, KeeperException { BackupOptions options = new BackupOptions(); CmdLineParser parser = new CmdLineParser(options); try { parser.parseArgument(args); if (options.help) { usage(parser, 0); } } catch (CmdLineException e) { if (!options.help) { System.err.println(e.getLocalizedMessage()); } usage(parser, options.help ? 0 : 1); } if (options.verbose) { LoggingUtils.enableDebugLogging(Backup.class.getPackage().getName()); } Backup backup = new Backup(options); OutputStream os; if ("-".equals(options.outputFile)) { os = System.out; } else { os = new BufferedOutputStream(new FileOutputStream(options.outputFile)); } try { if (options.compress) { os = new GZIPOutputStream(os); } backup.backup(os); } finally { os.flush(); Closeables.close(os, true); } } }