/*******************************************************************************
* Copyright (c) 2014 EURA NOVA.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v2.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* Aldemar Reynaga - initial API and implementation
* Salim Jouili - initial API and implementation
******************************************************************************/
package com.steffi.traversal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.zeromq.ZMQ;
import org.zeromq.ZMQ.Socket;
import com.steffi.common.Configuration;
import com.steffi.common.Configuration.Key;
import com.steffi.networking.messages.AddressVertexRepMsg;
import com.steffi.networking.messages.AddressVertexReqMsg;
import com.steffi.networking.messages.ClusterAddressesRep;
import com.steffi.networking.messages.Message;
import com.steffi.networking.messages.MessageType;
import com.steffi.networking.messages.SearchEndMsg;
import com.steffi.networking.messages.SearchRepMsg;
import com.steffi.networking.messages.SearchReqMsg;
/**
* @author Aldemar Reynaga
* Process running inside a traversal worker that coordinates the distributed traversal
*/
public class TraversalManager {
private int id;
private TraversalState traversalState;
private Map<String, String> clusterAddresses;
private long startVertexId;
private int hops;
private MatchEvaluatorConf matchConf;
private List<EdgeTraversalConf> traversalConfs;
private UUID searchId;
private boolean runningTraversal;
private Object lock;
private boolean criticalError;
private boolean stopWhenFound;
private boolean logStatistics;
private int managerIndex;
private ZMQ.Context context;
private AtomicInteger searchRepCounter;
private String clientIp;
private Map<String, SearchClientThread> clientThreads;
public TraversalManager(ZMQ.Context context, int id) {
this.id = id;
this.logStatistics = Configuration.getProperty(Key.LOG_STATISTICS).equals("true");
this.context = context;
}
public void init(UUID searchId, long startVertexId, int hops, MatchEvaluatorConf matchConf,
List<EdgeTraversalConf> traversalConfs, String clientIp, int managerIndex) throws Exception {
if (logStatistics)
this.searchRepCounter = new AtomicInteger(0);
this.managerIndex = managerIndex;
this.startVertexId = startVertexId;
this.hops = hops;
this.matchConf = matchConf;
this.traversalConfs = traversalConfs;
this.traversalState = new TraversalState();
this.searchId = searchId;
this.runningTraversal = false;
this.lock = new String("xyz");
this.clientIp = clientIp;
this.criticalError = false;
if (clusterAddresses == null) {
loadAddressesIps();
initClientThreads();
}
stopWhenFound = matchConf.getEvaluation().equals(Evaluation.INCLUDE_AND_STOP);
}
private SearchReqMsg prepareReqMessage(UUID searchMsgId, List<ReducedVertexPath> vertexPaths) {
SearchReqMsg searchReqMsg = new SearchReqMsg();
searchReqMsg.setMatchConf(matchConf);
searchReqMsg.setMaxHops(hops);
searchReqMsg.setSearchId(searchId);
searchReqMsg.setTraversalConfs(traversalConfs);
searchReqMsg.setSearchMsgId(searchMsgId);
//searchReqMsg.setLastExplored(new ArrayList<VertexIdDepth>());
searchReqMsg.setVertexPaths(vertexPaths);
searchReqMsg.setManagerIndex(managerIndex);
return searchReqMsg;
}
private void initClientThreads() {
this.clientThreads = new ConcurrentHashMap<String, SearchClientThread>();
SearchClientThread clientThread = null;
for (Entry<String, String> entry : clusterAddresses.entrySet()) {
clientThread = new SearchClientThread(context, entry.getValue(), entry.getKey(), "tm_" +
entry.getKey() + "_" + this.id);
clientThreads.put(entry.getKey(), clientThread);
new Thread(clientThread).start();
}
}
private void sendSearchReqMessage(UUID searchMsgId, String address, List<ReducedVertexPath> vertexPaths) throws Exception {
SearchClientThread clientThread = clientThreads.get(address);
clientThread.sendMessage(prepareReqMessage(searchMsgId, vertexPaths));
}
public void closeClientThreads() {
if (clientThreads != null)
for (SearchClientThread ct : clientThreads.values())
ct.stop();
}
public void sendEndSearchMsgs(UUID searchId) throws Exception {
SearchEndMsg searchEndMsg = null;
for (SearchClientThread ct : clientThreads.values()) {
searchEndMsg = new SearchEndMsg();
searchEndMsg.setSearchId(searchId);
ct.sendMessage(searchEndMsg);
}
}
private String getStartAddress() throws Exception {
String address = null;
Socket requester = context.socket(ZMQ.REQ);
try {
requester.setIdentity(UUID.randomUUID().toString().getBytes());
requester.connect("tcp://" + clientIp + ":" + Configuration.getProperty(Configuration.Key.NODE_PORT));
AddressVertexReqMsg message = new AddressVertexReqMsg();
message.setCellIds(Arrays.asList(startVertexId));
requester.send(Message.convertMessageToBytes(message), 0);
byte[] reply = requester.recv(0);
AddressVertexRepMsg response = (AddressVertexRepMsg) Message.readFromBytes(reply);
address = response.getCellAddresses().get(startVertexId);
} finally {
requester.close();
}
return address;
}
private void loadAddressesIps() throws Exception {
Socket requester = context.socket(ZMQ.REQ);
try {
requester.setIdentity(UUID.randomUUID().toString().getBytes());
requester.connect("tcp://" + clientIp + ":" + Configuration.getProperty(Configuration.Key.NODE_PORT));
Message message = new Message(MessageType.CLUSTER_ADDRESSES_REQ);
requester.send(Message.convertMessageToBytes(message), 0);
byte[] reply = requester.recv(0);
ClusterAddressesRep adRep = (ClusterAddressesRep) Message.readFromBytes(reply);
clusterAddresses = adRep.getAddressesIp();
} finally {
requester.close();
}
}
public TraversalResults traverse() throws Exception {
String startAddress = getStartAddress();
List<ReducedVertexPath> vertexPaths = new ArrayList<ReducedVertexPath>();
ReducedVertexPath vertexPath = new ReducedVertexPath(startVertexId);
vertexPaths.add(vertexPath);
UUID searchMsgId = UUID.randomUUID();
traversalState.addPendingMessage(searchMsgId);
sendSearchReqMessage(searchMsgId, startAddress, vertexPaths); //Initial traversal task
runningTraversal = true;
synchronized (lock) {
while(runningTraversal) {
try {
lock.wait();
} catch (InterruptedException ie){
break;
}
}
}
if (criticalError)
throw new Exception("Critical error on the traversal");
sendEndSearchMsgs(searchId);
return traversalState.getResults();
}
public boolean isTraversalRunning() {
return runningTraversal;
}
private void checkTraversalEnd() {
if (stopWhenFound &&
!traversalState.results.getVertexPaths().isEmpty() ) {
synchronized (lock) {
runningTraversal = false;
if (logStatistics)
System.out.println("Search responses processed: " + searchRepCounter.get());
lock.notifyAll();
}
} else {
if (traversalState.isCompleted()) {
synchronized (lock) {
runningTraversal = false;
if (logStatistics)
System.out.println("Search responses processed: " + searchRepCounter.get());
lock.notifyAll();
}
}
}
}
public void registerSearchRepMsg(SearchRepMsg message) {
if (logStatistics)
searchRepCounter.incrementAndGet();
synchronized (traversalState) {
if (runningTraversal) {
if (!message.isOk()) {
criticalError = true;
System.out.println("Critical error on traversal!");
synchronized (lock) {
runningTraversal = false;
lock.notifyAll();
}
} else {
traversalState.addMessageResponse(message.getSearchMsgId());
traversalState.addPendingMessages(message.getSentSearchMsgIds());
traversalState.getResults().addManyVertexPaths(message.getTraversalResults().getVertexPaths());
checkTraversalEnd();
}
}
}
}
private class TraversalState {
private TraversalResults results;
private Set<UUID> pendingMessages;
private Set<UUID> messageResponses;
public TraversalState() {
this.results = new TraversalResultsImpl();
pendingMessages = new HashSet<UUID>();
messageResponses = new HashSet<UUID>();
}
public void addPendingMessages(List<UUID> uuids) {
for (UUID uuid : uuids)
addPendingMessage(uuid);
}
public void addPendingMessage(UUID uuid) {
if (!messageResponses.remove(uuid)) {
pendingMessages.add(uuid);
}
}
public void addMessageResponse(UUID uuid) {
if (!pendingMessages.remove(uuid)) {
messageResponses.add(uuid);
}
}
public boolean isCompleted() {
return (messageResponses.isEmpty() && pendingMessages.isEmpty());
}
public TraversalResults getResults() {
return results;
}
@Override
public String toString() {
return "PEND: " + pendingMessages.size() + " RESP: " + messageResponses.size();
}
}
}