/**
* 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.queue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.hadoop.io.Writable;
import org.goldenorb.Message;
import org.goldenorb.Messages;
import org.goldenorb.OrbPartitionCommunicationProtocol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class constructs an OutboundMessageQueue which collects Messages to be sent out to other partitions
* once a certain number has been collected.
*
*/
public class OutboundMessageQueue {
private Logger omqLogger;
private int numberOfPartitions;
private int maxMessages;
private Map<Integer,OrbPartitionCommunicationProtocol> orbClients;
private Class<? extends Message<? extends Writable>> messageClass;
private int partitionId;
PartitionMessagingObject pmo;
/**
* This constructs the OutboundMessageQueue and creates the underlying data structures to be sent to a
* PartitionMessagingObject.
*
* @param numberOfPartitions
* - the number of partitions to be used in the Job
* @param maxMessages
* - the maximum number of messages to be queued up before a send operation
* @param orbClients
* - a Map of the communication protocol used by each Orb client
* @param messageClass
* - the type of Message to be queued. User-defined by extension of Message
* @param partitionId
* - the ID of the partition that creates and owns this OutboundMessageQueue
*/
public OutboundMessageQueue(int numberOfPartitions,
int maxMessages,
Map<Integer,OrbPartitionCommunicationProtocol> orbClients,
Class<? extends Message<? extends Writable>> messageClass,
int partitionId) {
omqLogger = LoggerFactory.getLogger(OutboundMessageQueue.class);
this.numberOfPartitions = numberOfPartitions;
this.maxMessages = maxMessages;
this.orbClients = orbClients;
this.messageClass = messageClass;
this.partitionId = partitionId;
List<Map<String,List<Message<? extends Writable>>>> partitionMessageMapsList;
List<Integer> partitionMessageCounter;
// creates a HashMap<Vertex,Message List> for each outbound partition
partitionMessageMapsList = new ArrayList<Map<String,List<Message<? extends Writable>>>>(
numberOfPartitions);
for (int i = 0; i < numberOfPartitions; i++) {
partitionMessageMapsList.add(Collections
.synchronizedMap(new HashMap<String,List<Message<? extends Writable>>>()));
}
// initializes a message counter for each outbound partition
partitionMessageCounter = new ArrayList<Integer>(numberOfPartitions);
for (int i = 0; i < numberOfPartitions; i++) {
partitionMessageCounter.add(0);
}
this.pmo = new PartitionMessagingObject(partitionMessageMapsList, partitionMessageCounter);
}
/**
* This method queues up a Message to be sent. Once the Message count reaches the maximum number, it sends
* the vertices via Hadoop RPC.
*
* @param m
* - a Message to be sent
*/
public void sendMessage(Message<? extends Writable> m) {
synchronized (pmo) {
// get the HashMap bin that is unique to the DestinationVertex
int messageHash = Math.abs(m.getDestinationVertex().hashCode()) % numberOfPartitions;
Map<String,List<Message<? extends Writable>>> currentPartition = pmo.partitionMessageMapsList
.get(messageHash);
Integer messageCounter;
synchronized (pmo.partitionMessageCounter) {
synchronized (currentPartition) {
messageCounter = pmo.partitionMessageCounter.get(messageHash);
messageCounter++; // increment the message counter
pmo.partitionMessageCounter.set(messageHash, messageCounter);
}
// if Vertex exists as a key, add the Message
// else create a new synchronized list for the key on demand, then put the list in the map
if (currentPartition.containsKey(m.getDestinationVertex())) {
currentPartition.get(m.getDestinationVertex()).add(m);
} else {
List<Message<? extends Writable>> messageList = Collections
.synchronizedList(new ArrayList<Message<? extends Writable>>());
messageList.add(m);
currentPartition.put(m.getDestinationVertex(), messageList);
}
// once the expected number of messages is met, begins the message sending operation
if (messageCounter >= maxMessages) {
Messages messagesToBeSent = new Messages(messageClass);
// collects the messages associated to each key and adds them to a Messages object to be sent
for (Collection<Message<? extends Writable>> ms : currentPartition.values()) {
for (Message<? extends Writable> message : ms) {
messagesToBeSent.add(message);
}
}
// logger stuff
omqLogger
.info(this.toString() + " Partition: " + Integer.toString(partitionId)
+ "Sending bulk messages. Count: " + messageCounter + ", " + messagesToBeSent.size());
omqLogger.info(messageClass.getName());
// sends the Messages to the partition as specified over RPC, then creates a fresh, empty Map in its
// place
orbClients.get(messageHash).sendMessages(messagesToBeSent);
pmo.partitionMessageMapsList.set(messageHash,
Collections.synchronizedMap(new HashMap<String,List<Message<? extends Writable>>>()));
pmo.partitionMessageCounter.set(messageHash, new Integer(0)); // reset counter to 0
}
}
}
}
/**
* Sends any remaining messages if the maximum number of messages is not met.
*/
public void sendRemainingMessages() {
for (int partitionID = 0; partitionID < numberOfPartitions; partitionID++) {
Messages messagesToBeSent = new Messages(messageClass);
for (Collection<Message<? extends Writable>> ms : pmo.partitionMessageMapsList.get(partitionID)
.values()) {
for (Message<? extends Writable> message : ms) {
messagesToBeSent.add(message);
}
}
if (messagesToBeSent.size() > 0) {
omqLogger.info("Partition {} sending bulk messages. Count: " + pmo.partitionMessageCounter.get(partitionID) + ", "
+ messagesToBeSent.size(), partitionID);
orbClients.get(partitionID).sendMessages(messagesToBeSent);
}
else {
omqLogger.debug("No messages to be sent from Partition {}", partitionID);
}
}
}
/**
* This inner class defines a PartitionMessagingObject, which is used strictly to encapsulate
* partitionMessageMapsList and partitionMessageCounter into one object for synchronization purposes.
*
*/
class PartitionMessagingObject {
List<Map<String,List<Message<? extends Writable>>>> partitionMessageMapsList;
List<Integer> partitionMessageCounter;
/**
* Constructor
*
* @param partitionMessageMapsList
* @param partitionMessageCounter
*/
public PartitionMessagingObject(List<Map<String,List<Message<? extends Writable>>>> partitionMessageMapsList,
List<Integer> partitionMessageCounter) {
this.partitionMessageMapsList = partitionMessageMapsList;
this.partitionMessageCounter = partitionMessageCounter;
}
/**
* Return the mapsList.
*/
public List<Map<String,List<Message<? extends Writable>>>> getMapsList() {
return partitionMessageMapsList;
}
/**
* Return the messageCounter.
*/
public List<Integer> getMessageCounter() {
return partitionMessageCounter;
}
}
}