package org.talend.esb.servicelocator.client.internal.zk;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.zookeeper.*;
import org.apache.zookeeper.KeeperException.Code;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.data.ACL;
import org.talend.esb.servicelocator.client.ServiceLocator.PostConnectAction;
import org.talend.esb.servicelocator.client.ServiceLocatorException;
import org.talend.esb.servicelocator.client.internal.NodePath;
import org.talend.esb.servicelocator.client.internal.RootNode;
import org.talend.esb.servicelocator.client.internal.ServiceLocatorBackend;
import org.talend.esb.servicelocator.client.internal.ServiceLocatorImpl;
import static org.talend.esb.servicelocator.client.internal.zk.ServiceLocatorACLs.LOCATOR_ACLS;
public class ZKBackend implements ServiceLocatorBackend {
public static final NodePath LOCATOR_ROOT_PATH = new NodePath("cxf-locator");
public static final Charset UTF8_CHAR_SET = Charset.forName("UTF-8");
private static final Logger LOG = Logger.getLogger(ServiceLocatorImpl.class
.getName());
private static final byte[] EMPTY_CONTENT = new byte[0];
// private static final PostConnectAction DO_NOTHING_ACTION = new PostConnectAction() {
// @Override
// public void process(ServiceLocator lc) {
// }
// };
private Set<PostConnectAction> postConnectActions = new HashSet<PostConnectAction>();
// private int sessionTimeout = 5000;
// private int connectionTimeout = 5000;
private boolean authentication;
private LocatorSettings settings = new LocatorSettings();
private volatile ZooKeeper zk;
private RootNodeImpl rootNode = new RootNodeImpl(this);
{
// postConnectActionList.add(DO_NOTHING_ACTION);
settings.setEndpoints("localhost:2181");
}
@Override
public RootNode connect() throws InterruptedException,
ServiceLocatorException {
if (!isConnected()) {
disconnect();
if (LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "Start connect session");
}
CountDownLatch connectionLatch = new CountDownLatch(1);
zk = createZooKeeper(connectionLatch);
if (authentication) {
authenticate();
}
boolean connected = connectionLatch.await(settings.getConnectionTimeout(),
TimeUnit.MILLISECONDS);
if (!connected) {
throw new ServiceLocatorException(
"Connection to Service Locator failed.");
}
for (PostConnectAction postConnectAction : postConnectActions) {
postConnectAction.process(null);
}
if (LOG.isLoggable(Level.FINER)) {
LOG.log(Level.FINER, "End connect session");
}
}
return rootNode;
}
@Override
public void disconnect() throws InterruptedException,
ServiceLocatorException {
if (zk != null) {
zk.close();
zk = null;
if (LOG.isLoggable(Level.FINER)) {
LOG.log(Level.FINER, "Disconnected service locator session.");
}
}
}
public boolean isConnected() {
return (zk != null) && zk.getState().equals(ZooKeeper.States.CONNECTED);
}
public RootNode getRootNode() throws InterruptedException,
ServiceLocatorException {
connect();
return rootNode;
}
public boolean nodeExists(NodePath path) throws ServiceLocatorException,
InterruptedException {
try {
return zk.exists(path.toString(), false) != null;
} catch (KeeperException e) {
throw locatorException(e);
}
}
public void createNode(NodePath path, CreateMode mode, byte[] content)
throws KeeperException, InterruptedException {
zk.create(path.toString(), content, getACLs(), mode);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Node " + path + " created as" + mode
+ "in ZooKeeper with content "
+ new String(content, UTF8_CHAR_SET));
}
}
public void setNodeData(NodePath path, byte[] content)
throws ServiceLocatorException, InterruptedException {
try {
zk.setData(path.toString(), content, -1);
} catch (KeeperException e) {
throw locatorException(e);
}
}
public boolean deleteNode(NodePath path, boolean canHaveChildren)
throws KeeperException, InterruptedException {
try {
zk.delete(path.toString(), -1);
return true;
} catch (KeeperException e) {
if (e.code().equals(Code.NOTEMPTY) && canHaveChildren) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Some other client created children nodes in the node"
+ path
+ " concurrently. Therefore, we can not delete it.");
}
return false;
} else {
throw e;
}
}
}
public <T> List<T> getChildren(NodePath path, NodeMapper<T> mapper)
throws ServiceLocatorException, InterruptedException {
List<String> encoded;
try {
encoded = zk.getChildren(path.toString(), false);
} catch (KeeperException e) {
throw locatorException(e);
}
List<T> boundChildren = new ArrayList<T>(encoded.size());
for (String oneEncoded : encoded) {
String notEncoded = NodePath.decode(oneEncoded);
T boundChild = mapper.map(notEncoded);
boundChildren.add(boundChild);
}
return boundChildren;
}
public byte[] getContent(NodePath path) throws ServiceLocatorException,
InterruptedException {
try {
byte[] content = zk.getData(path.toString(), false, null);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Retrieved the following content for node " + path);
LOG.fine(new String(content, UTF8_CHAR_SET));
}
return content;
} catch (KeeperException e) {
throw locatorException(e);
}
}
public void ensurePathExists(NodePath path, CreateMode mode)
throws ServiceLocatorException, InterruptedException {
ensurePathExists(path, mode, EMPTY_CONTENT);
}
public void ensurePathExists(NodePath path, CreateMode mode, byte[] content)
throws ServiceLocatorException, InterruptedException {
try {
if (!nodeExists(path)) {
createNode(path, mode, content);
} else {
if (mode.isEphemeral()) {
deleteNode(path, false);
createNode(path, mode, content);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Ephemeral node " + path + " was recreated.");
}
} else if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Node " + path + " already exists.");
}
if (!Arrays.equals(EMPTY_CONTENT, content) && (content.length != 0)) setNodeData(path, content);
}
} catch (KeeperException e) {
if (!e.code().equals(Code.NODEEXISTS)) {
throw locatorException(e);
} else {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Some other client created node" + path
+ " concurrently.");
}
}
}
}
/**
* @param path Path to the node to be removed
* @param canHaveChildren If <code>false</code> method throws an exception in case we
* have {@link KeeperException} with code
* {@link KeeperException.Code.NOTEMPTY NotEmpty}. If
* <code>true</code>, node just not be deleted in case we have
* Keeper {@link KeeperException.NotEmptyException
* NotEmptyException}.
* @throws ServiceLocatorException
* @throws InterruptedException
*/
public void ensurePathDeleted(NodePath path, boolean canHaveChildren)
throws ServiceLocatorException, InterruptedException {
try {
if (deleteNode(path, canHaveChildren)) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Node " + path + " deteted.");
}
} else {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Node " + path
+ " cannot be deleted because it has children.");
}
}
} catch (KeeperException e) {
if (e.code().equals(Code.NONODE)) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Node" + path + " already deleted.");
}
} else {
throw locatorException(e);
}
}
}
@Override
public void addPostConnectAction(PostConnectAction postConnectAction) {
postConnectActions.add(postConnectAction);
}
@Override
public void removePostConnectAction(PostConnectAction postConnectAction) {
postConnectActions.remove(postConnectAction);
}
/**
* Specify the endpoints of all the instances belonging to the service
* locator ensemble this object might potentially be talking to when
* {@link #connect() connecting}. The object will one by one pick an
* endpoint (the order is non-deterministic) to connect to the service
* locator until a connection is established.
*
* @param endpoints comma separated list of endpoints,each corresponding to a
* service locator instance. Each endpoint is specified as a
* host:port pair. At least one endpoint must be specified. Valid
* exmaples are: "127.0.0.1:2181" or
* "sl1.example.com:3210, sl2.example.com:3210, sl3.example.com:3210"
*/
public void setLocatorEndpoints(String endpoints) {
settings.setEndpoints(endpoints);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Locator endpoints set to " + settings.getEndpoints());
}
}
/**
* Specify the time out of the session established at the server. The
* session is kept alive by requests sent by this client object. If the
* session is idle for a period of time that would timeout the session, the
* client will send a PING request to keep the session alive.
*
* @param timeout timeout in milliseconds, must be greater than zero and less
* than 60000.
*/
public void setSessionTimeout(int timeout) {
settings.setSessionTimeout(timeout);
}
/**
* Specify the time this client waits {@link #connect() for a connection to
* get established}.
*
* @param timeout timeout in milliseconds, must be greater than zero
*/
public void setConnectionTimeout(int timeout) {
settings.setConnectionTimeout(timeout);
}
public void setUserName(String userName) {
settings.setUser(userName);
}
public void setPassword(String passWord) {
settings.setPassword(passWord);
}
private void initializeRootNode() throws ServiceLocatorException,
InterruptedException {
rootNode.ensureExists();
authentication = rootNode.isAuthenticationEnabled();
}
private void authenticate() throws ServiceLocatorException {
if (settings.getUser() == null) {
throw new ServiceLocatorException(
"Service Locator server requires authentication, but no user is defined.");
}
zk.addAuthInfo("sl", settings.getAuthInfo());
}
private List<ACL> getACLs() {
return authentication ? LOCATOR_ACLS : Ids.OPEN_ACL_UNSAFE;
}
protected ZooKeeper createZooKeeper(CountDownLatch connectionLatch)
throws ServiceLocatorException {
try {
return new ZooKeeper(settings.getEndpoints(), settings.getSessionTimeout(),
new WatcherImpl(connectionLatch));
} catch (IOException e) {
throw new ServiceLocatorException(
"A network failure occured when connecting to the ZooKeeper server",
e);
}
}
private ServiceLocatorException locatorException(Exception e) {
if (LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "The service locator server signaled an error",
e);
}
return new ServiceLocatorException(
"The service locator server signaled an error.", e);
}
public interface NodeMapper<T> {
T map(String nodeName) throws ServiceLocatorException,
InterruptedException;
}
public class WatcherImpl implements Watcher {
private CountDownLatch connectionLatch;
public WatcherImpl(CountDownLatch connectionLatch) {
this.connectionLatch = connectionLatch;
}
@Override
public void process(WatchedEvent event) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Event with state " + event.getState() + " sent.");
}
KeeperState eventState = event.getState();
try {
if (eventState == KeeperState.SyncConnected) {
try {
initializeRootNode();
} catch (ServiceLocatorException e) {
KeeperException zke = (KeeperException) e.getCause();
if (zke.code().equals(KeeperException.Code.NOAUTH)) {
authenticate();
initializeRootNode();
}
}
//fix for TESB-9642
// for (PostConnectAction postConnectAction : postConnectActionList) {
// postConnectAction.process(null);
// }
//moved to connect() method
connectionLatch.countDown();
} else if (eventState == KeeperState.Expired) {
connect();
}
} catch (InterruptedException e) {
if (LOG.isLoggable(Level.SEVERE)) {
LOG.log(Level.SEVERE,
"An InterruptedException was thrown while waiting for an answer from the"
+ "Service Locator", e);
}
} catch (ServiceLocatorException e) {
if (LOG.isLoggable(Level.SEVERE)) {
LOG.log(Level.SEVERE,
"Failed to execute an request to Service Locator.",
e);
}
}
}
}
}