package eu.leads.processor.deploy;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import eu.leads.processor.Module;
import eu.leads.processor.execute.operators.OperatorCompletionEvent;
import eu.leads.processor.execute.operators.OperatorType;
import eu.leads.processor.execute.operators.ReadOperator;
import eu.leads.processor.plan.*;
import eu.leads.processor.query.QueryContext;
import eu.leads.processor.query.QueryState;
import eu.leads.processor.sql.PlanNode;
import eu.leads.processor.utils.InfinispanUtils;
import eu.leads.processor.utils.StdOutputWriter;
import eu.leads.processor.utils.StringConstants;
import org.apache.activemq.command.ActiveMQTextMessage;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.TextMessage;
import java.io.IOException;
import java.io.StringReader;
import java.util.*;
import java.util.concurrent.ConcurrentMap;
/**
* Created with IntelliJ IDEA.
* User: vagvaz
* Date: 10/29/13
* Time: 10:44 AM
* To change this template use File | Settings | File Templates.
*/
public class LeadsDeployerService extends Module {
private EventBus eventBus;
private Queue<Message> incoming;
private ObjectMapper mapper;
private final Object mutex = new Object();
private Set<String> ignore;
private Map<String, ExecutionPlanInfo> info;
private ConcurrentMap queries;
private final String executorQueue = StringConstants.NODEEXECUTORQUEUE;
private final String deployerQueue = StringConstants.DEPLOYERQUEUE;
private final String userInterfaceMQ = StringConstants.UIMANAGERQUEUE;
public LeadsDeployerService(String url, String name) throws Exception {
super(url, name);
com.subscribeToQueue(deployerQueue);
com.createQueuePublisher(executorQueue);
com.setQueueMessageListener(this);
eventBus = null;
incoming = new LinkedList<Message>();
mapper = new ObjectMapper();
ignore = new HashSet<String>();
ignore.add(OperatorType.toString(OperatorType.READ));
// ignore.add(OperatorType.toString(OperatorType.OUTPUT));
info = new HashMap<String, ExecutionPlanInfo>();
queries = InfinispanUtils.getOrCreatePersistentMap(StringConstants.QUERIESCACHE);
}
public LeadsDeployerService(String url, String name, EventBus eventBus) throws Exception {
super(url, name);
this.eventBus = eventBus;
this.eventBus.register(this);
com.subscribeToQueue(deployerQueue);
com.createQueuePublisher(executorQueue);
com.setQueueMessageListener(this);
incoming = new LinkedList<Message>();
mapper = new ObjectMapper();
//Ignore operators that should not be deployed.
ignore = new HashSet<String>();
ignore.add(OperatorType.toString(OperatorType.READ));
ignore.add(OperatorType.toString(OperatorType.OUTPUT));
info = new HashMap<String, ExecutionPlanInfo>();
queries = InfinispanUtils.getOrCreatePersistentMap(StringConstants.QUERIESCACHE);
}
@Override
protected void run() throws Exception {
while (isRunning()) {
Queue<Message> unprocessed = null;
synchronized (mutex) {
if (incoming.size() > 0) {
unprocessed = incoming;
incoming = new LinkedList<Message>();
} else {
mutex.wait();
}
}
if (unprocessed != null) {
while (!unprocessed.isEmpty()) {
TextMessage message = (TextMessage) unprocessed.poll();
String type = message.getStringProperty("type");
if (type.equals("execute")) {
//Read Query Context from message
QueryContext context = mapper.readValue(message.getStringProperty("context"), QueryContext.class);
StdOutputWriter.getInstance().println("QueryDeployer received an execution plan for query " + context.getQueryId());
String planString = message.getText();
ExecutionPlan plan = mapper.readValue(planString, ExecutionPlan.class);
initializeExecution(plan, context);
startExecution(plan, context);
}
}
}
}
}
//Start the execution of the plan
private void startExecution(ExecutionPlan plan, QueryContext context) {
Collection<String> sources = plan.getSources();
//For all the source nodes in the plan deploy
for (String source : sources) {
ExecutionPlanNode node = (ExecutionPlanNode) plan.getNode(source);
// A read operator is not Deployed, since we just read from the appropriate KVS cache
//thus we complete and try to get the next operator.
if (node.getOperatorType().equals(OperatorType.READ)) {
node.setStatus(NodeStatus.COMPLETED);
node = getNextOperator(info.get(context.getQueryId()), node.getName());
}
//The next operator is not ready to be deployed (not all the inputs are completed).
if (node == null)
continue;
//When we come across the output operator then we complete the query
//otherwise we deploy the operator.
if (node.getOperatorType().equals(OperatorType.OUTPUT)) {
completeQuery(info.get(context.getQueryId()));
} else {
deploy(node, info.get(context.getQueryId()));
}
}
}
//Initialize the Execution of a plan
private void initializeExecution(ExecutionPlan plan, QueryContext context) {
//Create an ExecutionPlanInfo which tracks the listeners and structures created for executing an execution plan.
ExecutionPlanInfo planInfo = new ExecutionPlanInfo(plan, context);
info.put(context.getQueryId(), planInfo);
Collection<PlanNode> col = plan.getNodes();
// Iterator<PlanNode> iterator = col.iterator();
//Inform monitor service for the initialization of the execution plan.
InitializeExecutionPlanEvent event = new InitializeExecutionPlanEvent();
eventBus.post(event);
}
//Event handler to handle the completion of an operator in order to deploy the next operator.
@Subscribe
public void operatorCompletion(OperatorCompletionEvent e) {
ExecutionPlanInfo planInfo = info.get(e.getQueryId());
ExecutionPlanNode node = (ExecutionPlanNode) planInfo.getPlan().getNode(e.getOperator());
node.setStatus(NodeStatus.COMPLETED);
//If there is a sort operator then we must update the query structure in the queriesCache
//We must know that a query has a sorted result in order to present tuples with the correct order
if (node.getOperatorType().equals(OperatorType.SORT)) {
String query = (String) queries.get(planInfo.getContext().getQueryId());
try {
//Update query structure in order to read sorted results in the correct sequence
JsonNode root = mapper.readTree(query);
ObjectNode nodeObject = (ObjectNode) root;
nodeObject.put("isSorted", mapper.writeValueAsString(true));
queries.put(planInfo.getContext().getQueryId(), nodeObject.toString());
} catch (IOException e1) {
e1.printStackTrace();
}
}
//deploy next operator
node = getNextOperator(planInfo, e.getOperator());
deploy(node, planInfo);
}
//Get next operator for the operator in the plan stored in planInfo
private ExecutionPlanNode getNextOperator(ExecutionPlanInfo planInfo, String operator) {
ExecutionPlanNode node = (ExecutionPlanNode) planInfo.getPlan().getNode(operator);
node = (ExecutionPlanNode) planInfo.getPlan().getNode(node.getOutput());
//if there is the output operator then we have completed the plan execution.
if (OperatorType.fromString(node.getType()) == OperatorType.OUTPUT) {
completeQuery(planInfo);
return null;
} else {
//check if all the node sources have been completed if not return null
for (String source : node.getSources()) {
ExecutionPlanNode inputNode = (ExecutionPlanNode) planInfo.getPlan().getNode(source);
if (inputNode.getStatus() != NodeStatus.COMPLETED)
return null;
}
}
return node;
}
//Deploy operator node
private void deploy(ExecutionPlanNode node, ExecutionPlanInfo planInfo) {
if (node == null)
return;
TextMessage message = new ActiveMQTextMessage();
ArrayList<String> inputs = null;
//Resolve input in order to pass the correct input cache argument to the operator
try {
inputs = resolveInput(node, planInfo);
} catch (Exception e) {
e.printStackTrace();
}
//Right now this is necessary for join operator where there are 2 inputs.
StringBuilder builder = new StringBuilder();
if(inputs != null){
for (String input : inputs)
builder.append(input + ",");
}
try {
//Prepare JMS message send it and set the node state to RUNNING
String input = builder.toString();
input = input.substring(0, input.length() - 1);
message.setStringProperty("input", input);
message.setStringProperty("type", "executeOperator");
message.setStringProperty("queryId", planInfo.getContext().getQueryId());
message.setText(mapper.writeValueAsString(node));
com.publishToQueue(message, executorQueue);
node.setStatus(NodeStatus.RUNNING);
} catch (JMSException e) {
e.printStackTrace();
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
//Resolve Input
private ArrayList<String> resolveInput(ExecutionPlanNode node, ExecutionPlanInfo planInfo) {
ArrayList<String> result = new ArrayList<String>();
List<String> sources = node.getSources();
for (String source : sources) { //for each input of the node
ExecutionPlanNode inputNode = (ExecutionPlanNode) planInfo.getPlan().getNode(source);
//if READ Operator then the input is the name of the table
if (inputNode.getType().equals(OperatorType.toString(OperatorType.READ))) {
ReadOperator read = (ReadOperator) inputNode;
result.add(read.getTable().getName());
} else { // otherwise the input is the name of the operator.
result.add(inputNode.getName());
}
}
return result;
}
//Complete Query
//Update query structure stored in KVS to store the appropriate output cache and
//set the query state to Completed
private void completeQuery(ExecutionPlanInfo planInfo) {
planInfo.finalizePlan();
String value = (String) queries.get(planInfo.getContext().getQueryId());
try {
JsonNode root = mapper.readTree(new StringReader(value));
((ObjectNode) root).put("queryState", mapper.writeValueAsString(QueryState.COMPLETED));
((ObjectNode) root).put("output", mapper.writeValueAsString(planInfo.getPlan().getOutput().getSources().get(0) + ":"));
queries.put(planInfo.getContext().getQueryId(), root.toString());
//inform user interface manager that the query has been processed
sendMessageToUIManager(planInfo.getContext().getQueryId());
} catch (IOException e) {
e.printStackTrace();
}
}
private void sendMessageToUIManager(String queryId) {
TextMessage msg = new ActiveMQTextMessage();
try {
msg.setText(queryId);
msg.setStringProperty("type", "queryCompletion");
// System.err.println("sending to user Interface ");
com.publishToDestination(msg, userInterfaceMQ);
} catch (JMSException e) {
e.printStackTrace();
}
}
@Override
public void onMessage(Message message) {
// System.err.println(this.getClass().toString() + " received msg");
synchronized (mutex) {
incoming.add(message);
mutex.notify();
}
}
@Override
protected void triggerShutdown() {
synchronized (mutex) {
mutex.notify();
}
super.triggerShutdown();
}
}