/** * Elector.java * * Copyright 2013 the original author or authors. * * We 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.niolex.election; import java.io.IOException; import java.util.Collections; import java.util.List; import org.apache.niolex.commons.codec.StringUtil; import org.apache.niolex.commons.collection.CollectionUtil; import org.apache.niolex.zookeeper.core.TempNodeAutoCreator; import org.apache.niolex.zookeeper.core.ZKListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This Elector is for P2P nodes elect one leader. * We use zookeeper for sequential order. The first registered node will * be elected as leader. If this leader goes down, the second one will * take his place, and so on. * <p> * If network problem occurred and the current session expired, we will * re-create the temporary node again. * * @author <a href="mailto:xiejiyun@foxmail.com">Xie, Jiyun</a> * @version 1.0.0 * @since 2013-12-5 */ public class Elector extends TempNodeAutoCreator implements ZKListener { protected static final Logger LOG = LoggerFactory.getLogger(Elector.class); /** * Listen to the changes of elect leader. * * @author <a href="mailto:xiejiyun@foxmail.com">Xie, Jiyun</a> * @version 1.0.0 * @since 2013-12-6 */ public static interface Listener { /** * Leader changed to this new address. * * @param address the new leader */ public void leaderChange(String address); /** * The current node need to run as new leader. */ public void runAsLeader(); } /** * The election base path. This path is guaranteed not end with "/" */ private final String basePath; /** * The current listener. */ private final Listener listn; /** * The current leader absolute path. */ private volatile String leaderPath; /** * Create a new Elector under this base path.<br> * User need to call {@link #register(String)} to register this node if he * want to elect the leader. * * @param clusterAddress the zookeeper cluster servers address list * @param sessionTimeout the session timeout in microseconds * @param basePath the ZK base path of this elector * @param listn the leader change listener * @throws IOException in cases of network failure * @throws IllegalArgumentException if sessionTimeout is too small */ public Elector(String clusterAddress, int sessionTimeout, String basePath, Listener listn) throws IOException { super(clusterAddress, sessionTimeout); if (basePath.endsWith("/")) { basePath = basePath.substring(0, basePath.length() - 1); } this.basePath = basePath; this.listn = listn; this.makeSurePathExists(basePath); this.onChildrenChange(watchChildren(basePath, this)); } /** * Register this address to the election. Please note that we can only * manage one address here. So do not call this method multiple times. * * @param address the address of this node * @return true if this address is registered, false if this elector already has another address */ public synchronized boolean register(String address) { if (selfPath != null) { return false; } boolean r = autoCreateTempNode(basePath + "/Elector-", StringUtil.strToUtf8Byte(address), true); LOG.info("A new Elector joined with path: {}.", selfPath); return r; } /** * Give up the current node. That means we don't want to elect the leader any more. * * @return true if give up success, false if not registered */ public synchronized boolean giveUp() { if (selfPath == null) { return false; } this.deleteNode(selfPath); LOG.info("The current node: {} now given up.", selfPath); selfPath = null; return true; } /** * @return the current node zookeeper path */ public String getCurrentPath() { return selfPath; } /** * This is the override of super method. * @see org.apache.niolex.zookeeper.core.ZKListener#onDataChange(byte[]) */ @Override public void onDataChange(byte[] data) { // We only care about children } /** * This is the override of super method. * @see org.apache.niolex.zookeeper.core.ZKListener#onChildrenChange(java.util.List) */ @Override public synchronized void onChildrenChange(List<String> list) { if (CollectionUtil.isEmpty(list)) { return; } // The absolute leader path. String leader = basePath + "/" + Collections.min(list); if (leader.equals(leaderPath)) { return; } else { if (leader.equals(selfPath)) { LOG.info("The current node is now run as the leader."); listn.runAsLeader(); } else { String address = this.getDataAsStr(leader); LOG.info("The leader address is changed to: {}.", address); listn.leaderChange(address); } leaderPath = leader; } } }