/**
* Licensed to Ravel, Inc. under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Ravel, Inc. 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.goldenorb.zookeeper;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
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.goldenorb.conf.OrbConfigurable;
import org.goldenorb.conf.OrbConfiguration;
import org.goldenorb.event.LeadershipChangeEvent;
import org.goldenorb.event.LostMemberEvent;
import org.goldenorb.event.MemberDataChangeEvent;
import org.goldenorb.event.NewMemberEvent;
import org.goldenorb.event.OrbCallback;
import org.goldenorb.event.OrbEvent;
import org.goldenorb.event.OrbExceptionEvent;
/**
* This class defines LeaderGroup, which forms the abstract structure used to coordinate between members.
*
*/
public class LeaderGroup<MEMBER_TYPE extends Member> implements OrbConfigurable {
private OrbConfiguration orbConf;
private OrbCallback orbCallback;
private String basePath;
private String myPath;
private MEMBER_TYPE member;
private Class<? extends Member> memberClass;
private ZooKeeper zk;
private boolean processWatchedEvents = true;
private volatile SortedMap<String,MEMBER_TYPE> members = Collections.synchronizedSortedMap(new TreeMap<String,MEMBER_TYPE>());
private SortedMap<String,MemberDataWatcher> watchers = Collections.synchronizedSortedMap(new TreeMap<String,MemberDataWatcher>());
private boolean fireEvents = false;
/**
* Constructor
*
* @param zk
* - ZooKeeper
* @param orbCallback
* - OrbCallback
*
* @param path
* - String
*
* @param member
* - MEMBER_TYPE
*
* @param memberClass
* - Class <? extends Member>
*/
public LeaderGroup(ZooKeeper zk,
OrbCallback orbCallback,
String path,
MEMBER_TYPE member,
Class<? extends Member> memberClass) {
this.memberClass = memberClass;
this.orbCallback = orbCallback;
this.basePath = path;
this.member = member;
this.zk = zk;
init();
}
/**
* Initializes the LeaderGroup.
*/
public void init() {
try {
ZookeeperUtils.notExistCreateNode(zk, basePath);
myPath = ZookeeperUtils.tryToCreateNode(zk, basePath + "/member", member,
CreateMode.EPHEMERAL_SEQUENTIAL);
members.put(myPath, member);
updateMembers();
fireEvents = true;
} catch (OrbZKFailure e) {
fireEvent(new OrbExceptionEvent(e));
}
}
/**
* Updates members in the group.
*/
@SuppressWarnings("unchecked")
public void updateMembers() throws OrbZKFailure {
synchronized (members) {
int numOfMembers = getNumOfMembers();
MEMBER_TYPE leaderMember;
if (numOfMembers != 0) {
leaderMember = getLeader();
} else {
leaderMember = null;
}
List<String> memberList;
try {
memberList = zk.getChildren(basePath, new WatchMembers());
} catch (KeeperException e) {
e.printStackTrace();
throw new OrbZKFailure(e);
} catch (InterruptedException e) {
e.printStackTrace();
throw new OrbZKFailure(e);
}
members.clear();
MEMBER_TYPE memberW = null;
for (String memberPath : memberList) {
MemberDataWatcher watcher = null;
if (watchers.containsKey(memberPath)) {
memberW = (MEMBER_TYPE) ZookeeperUtils.getNodeWritable(zk, basePath + "/" + memberPath,
memberClass, orbConf);
} else { // set watcher for new node
watcher = new MemberDataWatcher(memberPath);
memberW = (MEMBER_TYPE) ZookeeperUtils.getNodeWritable(zk, basePath + "/" + memberPath,
memberClass, orbConf, watcher);
if (memberW != null) {
watchers.put(memberPath, watcher);
}
}
if (memberW != null) {
members.put(memberPath, memberW);
}
}
// check for watchers that need to be made inactive and removed
Set<String> watcherSet = watchers.keySet();
ArrayList<String> toRemove = new ArrayList<String>();
if (watcherSet != null) {
for (String memberPath : watcherSet) {
if (!members.containsKey(memberPath)) {
watchers.get(memberPath).makeInactive();
toRemove.add(memberPath);
}
}
}
for (String memberPath : toRemove) {
watchers.remove(memberPath);
}
if (numOfMembers > getNumOfMembers()) {
fireEvent(new LostMemberEvent());
} else if (numOfMembers < getNumOfMembers()) {
fireEvent(new NewMemberEvent());
}
if (numOfMembers != 0 && getNumOfMembers() != 0) {
if (!leaderMember.equals(getLeader())) {
fireEvent(new LeadershipChangeEvent());
}
}
}
}
/**
* This class defines a Watcher for use with each member.
*
*/
public class WatchMembers implements Watcher {
/**
* Updates members based on receiving a Watcher event.
*
* @param event
* - WatchedEvent
*/
public void process(WatchedEvent event) {
if (LeaderGroup.this.isProcessWatchedEvents()) {
try {
updateMembers();
} catch (OrbZKFailure e) {
fireEvent(new OrbExceptionEvent(e));
}
}
}
}
/**
* This class defines a Watch for use with the data in each member.
*
*/
public class MemberDataWatcher implements Watcher {
private String nodePath;
private String nodeName;
private boolean active;
/**
* Constructor
*
* @param nodeName
* - String
*/
public MemberDataWatcher(String nodeName) {
this.nodePath = basePath + "/" + nodeName;
this.nodeName = nodeName;
// System.err.println("Watcher Created by: for Node : " + nodePath);
active = true;
}
/**
* Makes this member inactive.
*/
public void makeInactive() {
active = false;
}
/**
*
* @param event
* - WatchedEvent
*/
@SuppressWarnings("unchecked")
@Override
public void process(WatchedEvent event) {
if ((event.getType() != Event.EventType.NodeDeleted) && LeaderGroup.this.isProcessWatchedEvents()
&& active) {
try {
MEMBER_TYPE node = (MEMBER_TYPE) ZookeeperUtils.getNodeWritable(zk, nodePath, memberClass, orbConf,
this);
if (event.getType() == Event.EventType.NodeDataChanged) {
LeaderGroup.this.updateMembersData(nodeName, node);
}
} catch (OrbZKFailure e) {
e.printStackTrace();
LeaderGroup.this.fireEvent(new OrbExceptionEvent(e));
}
}
}
}
/**
* Updates a member's data.
*
* @param memberPath
* - String
* @param update
* - MEMBER_TYPE
*/
public void updateMembersData(String memberPath, MEMBER_TYPE update) {
if (update != null) {
members.put(memberPath, update);
fireEvent(new MemberDataChangeEvent());
}
}
/**
* Return the members
*/
public Collection<MEMBER_TYPE> getMembers() {
synchronized (members) {
return members.values();
}
}
/**
* Return the numOfMembers
*/
public int getNumOfMembers() {
synchronized (members) {
return members.size();
}
}
/**
* Return the true if the calling member is the leader.
*/
public boolean isLeader() {
synchronized (members) {
return member.equals(members.get(members.firstKey()));
}
}
/**
* Return the leader
*/
public MEMBER_TYPE getLeader() {
synchronized (members) {
return members.get(members.firstKey());
}
}
/**
*
* @param e
* - OrbEvent
*/
public void fireEvent(OrbEvent e) {
if (fireEvents) {
orbCallback.process(e);
}
}
/**
* Set the orbConf
*
* @param orbConf
* - OrbConfiguration
*/
public void setOrbConf(OrbConfiguration orbConf) {
this.orbConf = orbConf;
}
/**
* Return the orbConf
*/
public OrbConfiguration getOrbConf() {
return orbConf;
}
/**
* Return the membersPath
*/
public List<String> getMembersPath() {
ArrayList<String> paths = new ArrayList<String>();
Set<String> memberPaths = members.keySet();
for (String memberPath : memberPaths) {
paths.add(basePath + "/" + memberPath);
}
return paths;
}
/**
* Return the myPath
*/
public String getMyPath() {
return myPath;
}
/**
* Leaves the LeaderGroup.
*/
public void leave() {
this.processWatchedEvents = false;
try {
ZookeeperUtils.deleteNodeIfEmpty(zk, myPath);
} catch (OrbZKFailure e) {
fireEvent(new OrbExceptionEvent(e));
}
}
/**
* Return the rocessWatchedEvents
*/
protected boolean isProcessWatchedEvents() {
return processWatchedEvents;
}
/**
* Set the processWatchedEvents
*
* @param processWatchedEvents
* - boolean
*/
protected void setProcessWatchedEvents(boolean processWatchedEvents) {
this.processWatchedEvents = processWatchedEvents;
}
}