/**
* 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.inspector.manager;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooDefs.Perms;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Stat;
import org.apache.zookeeper.inspector.encryption.BasicDataEncryptionManager;
import org.apache.zookeeper.inspector.encryption.DataEncryptionManager;
import org.apache.zookeeper.inspector.logger.LoggerFactory;
import org.apache.zookeeper.retry.ZooKeeperRetry;
/**
* A default implementation of {@link ZooInspectorManager} for connecting to
* zookeeper instances
*/
public class ZooInspectorManagerImpl implements ZooInspectorManager {
private static final String A_VERSION = "ACL Version";
private static final String C_TIME = "Creation Time";
private static final String C_VERSION = "Children Version";
private static final String CZXID = "Creation ID";
private static final String DATA_LENGTH = "Data Length";
private static final String EPHEMERAL_OWNER = "Ephemeral Owner";
private static final String M_TIME = "Last Modified Time";
private static final String MZXID = "Modified ID";
private static final String NUM_CHILDREN = "Number of Children";
private static final String PZXID = "Node ID";
private static final String VERSION = "Data Version";
private static final String ACL_PERMS = "Permissions";
private static final String ACL_SCHEME = "Scheme";
private static final String ACL_ID = "Id";
private static final String SESSION_STATE = "Session State";
private static final String SESSION_ID = "Session ID";
/**
* The key used for the connect string in the connection properties file
*/
public static final String CONNECT_STRING = "hosts";
/**
* The key used for the session timeout in the connection properties file
*/
public static final String SESSION_TIMEOUT = "timeout";
/**
* The key used for the data encryption manager in the connection properties
* file
*/
public static final String DATA_ENCRYPTION_MANAGER = "encryptionManager";
private static final File defaultNodeViewersFile = new File(
"./config/defaultNodeVeiwers.cfg");
private static final File defaultConnectionFile = new File(
"./config/defaultConnectionSettings.cfg");
private DataEncryptionManager encryptionManager;
private String connectString;
private int sessionTimeout;
private ZooKeeper zooKeeper;
private final Map<String, NodeWatcher> watchers = new HashMap<String, NodeWatcher>();
protected boolean connected = true;
private Properties lastConnectionProps;
private String defaultEncryptionManager;
private String defaultTimeout;
private String defaultHosts;
/**
* @throws IOException
* - thrown if the default connection settings cannot be loaded
*
*/
public ZooInspectorManagerImpl() throws IOException {
loadDefaultConnectionFile();
}
/*
* (non-Javadoc)
*
* @see
* org.apache.zookeeper.inspector.manager.ZooInspectorManager#connect(java
* .util.Properties)
*/
public boolean connect(Properties connectionProps) {
try {
if (this.zooKeeper == null) {
String connectString = connectionProps
.getProperty(CONNECT_STRING);
String sessionTimeout = connectionProps
.getProperty(SESSION_TIMEOUT);
String encryptionManager = connectionProps
.getProperty(DATA_ENCRYPTION_MANAGER);
if (connectString == null || sessionTimeout == null) {
throw new IllegalArgumentException(
"Both connect string and session timeout are required.");
}
if (encryptionManager == null) {
this.encryptionManager = new BasicDataEncryptionManager();
} else {
Class<?> clazz = Class.forName(encryptionManager);
if (Arrays.asList(clazz.getInterfaces()).contains(
DataEncryptionManager.class)) {
this.encryptionManager = (DataEncryptionManager) Class
.forName(encryptionManager).newInstance();
} else {
throw new IllegalArgumentException(
"Data encryption manager must implement DataEncryptionManager interface");
}
}
this.connectString = connectString;
this.sessionTimeout = Integer.valueOf(sessionTimeout);
this.zooKeeper = new ZooKeeperRetry(connectString, Integer
.valueOf(sessionTimeout), new Watcher() {
public void process(WatchedEvent event) {
if (event.getState() == KeeperState.Expired) {
connected = false;
}
}
});
((ZooKeeperRetry) this.zooKeeper).setRetryLimit(10);
connected = ((ZooKeeperRetry) this.zooKeeper).testConnection();
return connected;
}
} catch (Exception e) {
e.printStackTrace();
}
connected = false;
return connected;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.zookeeper.inspector.manager.ZooInspectorManager#disconnect()
*/
public boolean disconnect() {
try {
if (this.zooKeeper != null) {
this.zooKeeper.close();
this.zooKeeper = null;
connected = false;
removeWatchers(this.watchers.keySet());
return true;
}
} catch (Exception e) {
LoggerFactory.getLogger().error(
"Error occurred while disconnecting from ZooKeeper server",
e);
}
return false;
}
/*
* (non-Javadoc)
*
* @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager#
* getChildren(java.lang.String)
*/
public List<String> getChildren(String nodePath) {
if (connected) {
try {
return zooKeeper.getChildren(nodePath, false);
} catch (Exception e) {
LoggerFactory.getLogger().error(
"Error occurred retrieving children of node: "
+ nodePath, e);
}
}
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager#getData
* (java.lang.String)
*/
public String getData(String nodePath) {
if (connected) {
try {
if (nodePath.length() == 0) {
nodePath = "/";
}
Stat s = zooKeeper.exists(nodePath, false);
if (s != null) {
return this.encryptionManager.decryptData(zooKeeper
.getData(nodePath, false, s));
}
} catch (Exception e) {
LoggerFactory.getLogger().error(
"Error occurred getting data for node: " + nodePath, e);
}
}
return null;
}
/*
* (non-Javadoc)
*
* @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager#
* getNodeChild(java.lang.String, int)
*/
public String getNodeChild(String nodePath, int childIndex) {
if (connected) {
try {
Stat s = zooKeeper.exists(nodePath, false);
if (s != null) {
return this.zooKeeper.getChildren(nodePath, false).get(
childIndex);
}
} catch (Exception e) {
LoggerFactory.getLogger().error(
"Error occurred retrieving child " + childIndex
+ " of node: " + nodePath, e);
}
}
return null;
}
/*
* (non-Javadoc)
*
* @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager#
* getNodeIndex(java.lang.String)
*/
public int getNodeIndex(String nodePath) {
if (connected) {
int index = nodePath.lastIndexOf("/");
if (index == -1
|| (!nodePath.equals("/") && nodePath.charAt(nodePath
.length() - 1) == '/')) {
throw new IllegalArgumentException("Invalid node path: "
+ nodePath);
}
String parentPath = nodePath.substring(0, index);
String child = nodePath.substring(index + 1);
if (parentPath != null && parentPath.length() > 0) {
List<String> children = this.getChildren(parentPath);
if (children != null) {
return children.indexOf(child);
}
}
}
return -1;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager#getACLs
* (java.lang.String)
*/
public List<Map<String, String>> getACLs(String nodePath) {
List<Map<String, String>> returnACLs = new ArrayList<Map<String, String>>();
if (connected) {
try {
if (nodePath.length() == 0) {
nodePath = "/";
}
Stat s = zooKeeper.exists(nodePath, false);
if (s != null) {
List<ACL> acls = zooKeeper.getACL(nodePath, s);
for (ACL acl : acls) {
Map<String, String> aclMap = new LinkedHashMap<String, String>();
aclMap.put(ACL_SCHEME, acl.getId().getScheme());
aclMap.put(ACL_ID, acl.getId().getId());
StringBuilder sb = new StringBuilder();
int perms = acl.getPerms();
boolean addedPerm = false;
if ((perms & Perms.READ) == Perms.READ) {
sb.append("Read");
addedPerm = true;
}
if (addedPerm) {
sb.append(", ");
}
if ((perms & Perms.WRITE) == Perms.WRITE) {
sb.append("Write");
addedPerm = true;
}
if (addedPerm) {
sb.append(", ");
}
if ((perms & Perms.CREATE) == Perms.CREATE) {
sb.append("Create");
addedPerm = true;
}
if (addedPerm) {
sb.append(", ");
}
if ((perms & Perms.DELETE) == Perms.DELETE) {
sb.append("Delete");
addedPerm = true;
}
if (addedPerm) {
sb.append(", ");
}
if ((perms & Perms.ADMIN) == Perms.ADMIN) {
sb.append("Admin");
addedPerm = true;
}
aclMap.put(ACL_PERMS, sb.toString());
returnACLs.add(aclMap);
}
}
} catch (InterruptedException e) {
LoggerFactory.getLogger().error(
"Error occurred retrieving ACLs of node: " + nodePath,
e);
} catch (KeeperException e) {
LoggerFactory.getLogger().error(
"Error occurred retrieving ACLs of node: " + nodePath,
e);
}
}
return returnACLs;
}
/*
* (non-Javadoc)
*
* @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager#
* getNodeMeta(java.lang.String)
*/
public Map<String, String> getNodeMeta(String nodePath) {
Map<String, String> nodeMeta = new LinkedHashMap<String, String>();
if (connected) {
try {
if (nodePath.length() == 0) {
nodePath = "/";
}
Stat s = zooKeeper.exists(nodePath, false);
if (s != null) {
nodeMeta.put(A_VERSION, String.valueOf(s.getAversion()));
nodeMeta.put(C_TIME, String.valueOf(s.getCtime()));
nodeMeta.put(C_VERSION, String.valueOf(s.getCversion()));
nodeMeta.put(CZXID, String.valueOf(s.getCzxid()));
nodeMeta
.put(DATA_LENGTH, String.valueOf(s.getDataLength()));
nodeMeta.put(EPHEMERAL_OWNER, String.valueOf(s
.getEphemeralOwner()));
nodeMeta.put(M_TIME, String.valueOf(s.getMtime()));
nodeMeta.put(MZXID, String.valueOf(s.getMzxid()));
nodeMeta.put(NUM_CHILDREN, String.valueOf(s
.getNumChildren()));
nodeMeta.put(PZXID, String.valueOf(s.getPzxid()));
nodeMeta.put(VERSION, String.valueOf(s.getVersion()));
}
} catch (Exception e) {
LoggerFactory.getLogger().error(
"Error occurred retrieving meta data for node: "
+ nodePath, e);
}
}
return nodeMeta;
}
/*
* (non-Javadoc)
*
* @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager#
* getNumChildren(java.lang.String)
*/
public int getNumChildren(String nodePath) {
if (connected) {
try {
Stat s = zooKeeper.exists(nodePath, false);
if (s != null) {
return s.getNumChildren();
}
} catch (Exception e) {
LoggerFactory.getLogger().error(
"Error occurred getting the number of children of node: "
+ nodePath, e);
}
}
return -1;
}
/*
* (non-Javadoc)
*
* @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager#
* hasChildren(java.lang.String)
*/
public boolean hasChildren(String nodePath) {
return getNumChildren(nodePath) > 0;
}
/*
* (non-Javadoc)
*
* @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager#
* isAllowsChildren(java.lang.String)
*/
public boolean isAllowsChildren(String nodePath) {
if (connected) {
try {
Stat s = zooKeeper.exists(nodePath, false);
if (s != null) {
return s.getEphemeralOwner() == 0;
}
} catch (Exception e) {
LoggerFactory.getLogger().error(
"Error occurred determining whether node is allowed children: "
+ nodePath, e);
}
}
return false;
}
/*
* (non-Javadoc)
*
* @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager#
* getSessionMeta()
*/
public Map<String, String> getSessionMeta() {
Map<String, String> sessionMeta = new LinkedHashMap<String, String>();
try {
if (zooKeeper != null) {
sessionMeta.put(SESSION_ID, String.valueOf(zooKeeper
.getSessionId()));
sessionMeta.put(SESSION_STATE, String.valueOf(zooKeeper
.getState().toString()));
sessionMeta.put(CONNECT_STRING, this.connectString);
sessionMeta.put(SESSION_TIMEOUT, String
.valueOf(this.sessionTimeout));
}
} catch (Exception e) {
LoggerFactory.getLogger().error(
"Error occurred retrieving session meta data.", e);
}
return sessionMeta;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.zookeeper.inspector.manager.ZooInspectorNodeTreeManager#createNode
* (java.lang.String, java.lang.String)
*/
public boolean createNode(String parent, String nodeName) {
if (connected) {
try {
String[] nodeElements = nodeName.split("/");
for (String nodeElement : nodeElements) {
String node = parent + "/" + nodeElement;
Stat s = zooKeeper.exists(node, false);
if (s == null) {
zooKeeper.create(node, this.encryptionManager
.encryptData(null), Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT);
parent = node;
}
}
return true;
} catch (Exception e) {
LoggerFactory.getLogger().error(
"Error occurred creating node: " + parent + "/"
+ nodeName, e);
}
}
return false;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.zookeeper.inspector.manager.ZooInspectorNodeTreeManager#deleteNode
* (java.lang.String)
*/
public boolean deleteNode(String nodePath) {
if (connected) {
try {
Stat s = zooKeeper.exists(nodePath, false);
if (s != null) {
List<String> children = zooKeeper.getChildren(nodePath,
false);
for (String child : children) {
String node = nodePath + "/" + child;
deleteNode(node);
}
zooKeeper.delete(nodePath, -1);
}
return true;
} catch (Exception e) {
LoggerFactory.getLogger().error(
"Error occurred deleting node: " + nodePath, e);
}
}
return false;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.zookeeper.inspector.manager.ZooInspectorNodeManager#setData
* (java.lang.String, java.lang.String)
*/
public boolean setData(String nodePath, String data) {
if (connected) {
try {
zooKeeper.setData(nodePath, this.encryptionManager
.encryptData(data), -1);
return true;
} catch (Exception e) {
LoggerFactory.getLogger().error(
"Error occurred setting data for node: " + nodePath, e);
}
}
return false;
}
/*
* (non-Javadoc)
*
* @seeorg.apache.zookeeper.inspector.manager.ZooInspectorManager#
* getConnectionPropertiesTemplate()
*/
public Pair<Map<String, List<String>>, Map<String, String>> getConnectionPropertiesTemplate() {
Map<String, List<String>> template = new LinkedHashMap<String, List<String>>();
template.put(CONNECT_STRING, Arrays
.asList(new String[] { defaultHosts }));
template.put(SESSION_TIMEOUT, Arrays
.asList(new String[] { defaultTimeout }));
template.put(DATA_ENCRYPTION_MANAGER, Arrays
.asList(new String[] { defaultEncryptionManager }));
Map<String, String> labels = new LinkedHashMap<String, String>();
labels.put(CONNECT_STRING, "Connect String");
labels.put(SESSION_TIMEOUT, "Session Timeout");
labels.put(DATA_ENCRYPTION_MANAGER, "Data Encryption Manager");
return new Pair<Map<String, List<String>>, Map<String, String>>(
template, labels);
}
/*
* (non-Javadoc)
*
* @see
* org.apache.zookeeper.inspector.manager.ZooInspectorManager#addWatchers
* (java.util.Collection,
* org.apache.zookeeper.inspector.manager.NodeListener)
*/
public void addWatchers(Collection<String> selectedNodes,
NodeListener nodeListener) {
// add watcher for each node and add node to collection of
// watched nodes
if (connected) {
for (String node : selectedNodes) {
if (!watchers.containsKey(node)) {
try {
watchers.put(node, new NodeWatcher(node, nodeListener,
zooKeeper));
} catch (Exception e) {
LoggerFactory.getLogger().error(
"Error occured adding node watcher for node: "
+ node, e);
}
}
}
}
}
/*
* (non-Javadoc)
*
* @see
* org.apache.zookeeper.inspector.manager.ZooInspectorManager#removeWatchers
* (java.util.Collection)
*/
public void removeWatchers(Collection<String> selectedNodes) {
// remove watcher for each node and remove node from
// collection of watched nodes
if (connected) {
for (String node : selectedNodes) {
if (watchers.containsKey(node)) {
NodeWatcher watcher = watchers.remove(node);
if (watcher != null) {
watcher.stop();
}
}
}
}
}
/**
* A Watcher which will re-add itself every time an event is fired
*
*/
public class NodeWatcher implements Watcher {
private final String nodePath;
private final NodeListener nodeListener;
private final ZooKeeper zookeeper;
private boolean closed = false;
/**
* @param nodePath
* - the path to the node to watch
* @param nodeListener
* the {@link NodeListener} for this node
* @param zookeeper
* - a {@link ZooKeeper} to use to access zookeeper
* @throws InterruptedException
* @throws KeeperException
*/
public NodeWatcher(String nodePath, NodeListener nodeListener,
ZooKeeper zookeeper) throws KeeperException,
InterruptedException {
this.nodePath = nodePath;
this.nodeListener = nodeListener;
this.zookeeper = zookeeper;
Stat s = zooKeeper.exists(nodePath, this);
if (s != null) {
zookeeper.getChildren(nodePath, this);
}
}
public void process(WatchedEvent event) {
if (!closed) {
try {
if (event.getType() != EventType.NodeDeleted) {
Stat s = zooKeeper.exists(nodePath, this);
if (s != null) {
zookeeper.getChildren(nodePath, this);
}
}
} catch (Exception e) {
LoggerFactory.getLogger().error(
"Error occured re-adding node watcherfor node "
+ nodePath, e);
}
nodeListener.processEvent(event.getPath(), event.getType()
.name(), null);
}
}
/**
*
*/
public void stop() {
this.closed = true;
}
}
/*
* (non-Javadoc)
*
* @seeorg.apache.zookeeper.inspector.manager.ZooInspectorManager#
* loadNodeViewersFile(java.io.File)
*/
public List<String> loadNodeViewersFile(File selectedFile)
throws IOException {
List<String> result = new ArrayList<String>();
if (defaultNodeViewersFile.exists()) {
FileReader reader = new FileReader(selectedFile);
try {
BufferedReader buff = new BufferedReader(reader);
try {
while (buff.ready()) {
String line = buff.readLine();
if (line != null && line.length() > 0) {
result.add(line);
}
}
} finally {
buff.close();
}
} finally {
reader.close();
}
}
return result;
}
private void loadDefaultConnectionFile() throws IOException {
if (defaultConnectionFile.exists()) {
Properties props = new Properties();
FileReader reader = new FileReader(defaultConnectionFile);
try {
props.load(reader);
} finally {
reader.close();
}
defaultEncryptionManager = props
.getProperty(DATA_ENCRYPTION_MANAGER) == null ? "org.apache.zookeeper.inspector.encryption.BasicDataEncryptionManager"
: props.getProperty(DATA_ENCRYPTION_MANAGER);
defaultTimeout = props.getProperty(SESSION_TIMEOUT) == null ? "5000"
: props.getProperty(SESSION_TIMEOUT);
defaultHosts = props.getProperty(CONNECT_STRING) == null ? "localhost:2181"
: props.getProperty(CONNECT_STRING);
} else {
defaultEncryptionManager = "org.apache.zookeeper.inspector.encryption.BasicDataEncryptionManager";
defaultTimeout = "5000";
defaultHosts = "localhost:2181";
}
}
/*
* (non-Javadoc)
*
* @seeorg.apache.zookeeper.inspector.manager.ZooInspectorManager#
* saveDefaultConnectionFile(java.util.Properties)
*/
public void saveDefaultConnectionFile(Properties props) throws IOException {
File defaultDir = defaultConnectionFile.getParentFile();
if (!defaultDir.exists()) {
if (!defaultDir.mkdirs()) {
throw new IOException(
"Failed to create configuration directory: "
+ defaultDir.getAbsolutePath());
}
}
if (!defaultConnectionFile.exists()) {
if (!defaultConnectionFile.createNewFile()) {
throw new IOException(
"Failed to create default connection file: "
+ defaultConnectionFile.getAbsolutePath());
}
}
FileWriter writer = new FileWriter(defaultConnectionFile);
try {
props.store(writer, "Default connection for ZooInspector");
} finally {
writer.close();
}
}
/*
* (non-Javadoc)
*
* @seeorg.apache.zookeeper.inspector.manager.ZooInspectorManager#
* saveNodeViewersFile(java.io.File, java.util.List)
*/
public void saveNodeViewersFile(File selectedFile,
List<String> nodeViewersClassNames) throws IOException {
if (!selectedFile.exists()) {
if (!selectedFile.createNewFile()) {
throw new IOException(
"Failed to create node viewers configuration file: "
+ selectedFile.getAbsolutePath());
}
}
FileWriter writer = new FileWriter(selectedFile);
try {
BufferedWriter buff = new BufferedWriter(writer);
try {
for (String nodeViewersClassName : nodeViewersClassNames) {
buff.append(nodeViewersClassName);
buff.append("\n");
}
} finally {
buff.flush();
buff.close();
}
} finally {
writer.close();
}
}
/*
* (non-Javadoc)
*
* @seeorg.apache.zookeeper.inspector.manager.ZooInspectorManager#
* setDefaultNodeViewerConfiguration(java.io.File, java.util.List)
*/
public void setDefaultNodeViewerConfiguration(
List<String> nodeViewersClassNames) throws IOException {
File defaultDir = defaultNodeViewersFile.getParentFile();
if (!defaultDir.exists()) {
if (!defaultDir.mkdirs()) {
throw new IOException(
"Failed to create configuration directory: "
+ defaultDir.getAbsolutePath());
}
}
saveNodeViewersFile(defaultNodeViewersFile, nodeViewersClassNames);
}
public List<String> getDefaultNodeViewerConfiguration() throws IOException {
return loadNodeViewersFile(defaultNodeViewersFile);
}
/*
* (non-Javadoc)
*
* @seeorg.apache.zookeeper.inspector.manager.ZooInspectorManager#
* getLastConnectionProps()
*/
public Properties getLastConnectionProps() {
return this.lastConnectionProps;
}
/*
* (non-Javadoc)
*
* @seeorg.apache.zookeeper.inspector.manager.ZooInspectorManager#
* setLastConnectionProps(java.util.Properties)
*/
public void setLastConnectionProps(Properties connectionProps) {
this.lastConnectionProps = connectionProps;
}
}