/*
* JORAM: Java(TM) Open Reliable Asynchronous Messaging
* Copyright (C) 2001 - 2010 ScalAgent Distributed Technologies
* Copyright (C) 1996 - 2000 Dyade
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA.
*
* Initial developer(s): Frederic Maistre (INRIA)
* Contributor(s): ScalAgent Distributed Technologies
*/
package org.objectweb.joram.mom.dest;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.TabularData;
import org.objectweb.joram.mom.messages.Message;
import org.objectweb.joram.mom.messages.MessageJMXWrapper;
import org.objectweb.joram.mom.notifications.AbortReceiveRequest;
import org.objectweb.joram.mom.notifications.AbstractRequestNot;
import org.objectweb.joram.mom.notifications.AcknowledgeRequest;
import org.objectweb.joram.mom.notifications.BrowseReply;
import org.objectweb.joram.mom.notifications.BrowseRequest;
import org.objectweb.joram.mom.notifications.ClientMessages;
import org.objectweb.joram.mom.notifications.DenyRequest;
import org.objectweb.joram.mom.notifications.ExceptionReply;
import org.objectweb.joram.mom.notifications.FwdAdminRequestNot;
import org.objectweb.joram.mom.notifications.QueueMsgReply;
import org.objectweb.joram.mom.notifications.ReceiveRequest;
import org.objectweb.joram.mom.notifications.TopicMsgsReply;
import org.objectweb.joram.mom.notifications.WakeUpNot;
import org.objectweb.joram.mom.util.DMQManager;
import org.objectweb.joram.shared.DestinationConstants;
import org.objectweb.joram.shared.MessageErrorConstants;
import org.objectweb.joram.shared.admin.AdminReply;
import org.objectweb.joram.shared.admin.AdminRequest;
import org.objectweb.joram.shared.admin.ClearQueue;
import org.objectweb.joram.shared.admin.DeleteQueueMessage;
import org.objectweb.joram.shared.admin.GetDMQSettingsReply;
import org.objectweb.joram.shared.admin.GetDMQSettingsRequest;
import org.objectweb.joram.shared.admin.GetNbMaxMsgRequest;
import org.objectweb.joram.shared.admin.GetNumberReply;
import org.objectweb.joram.shared.admin.GetPendingMessages;
import org.objectweb.joram.shared.admin.GetPendingRequests;
import org.objectweb.joram.shared.admin.GetQueueMessage;
import org.objectweb.joram.shared.admin.GetQueueMessageIds;
import org.objectweb.joram.shared.admin.GetQueueMessageIdsRep;
import org.objectweb.joram.shared.admin.GetQueueMessageRep;
import org.objectweb.joram.shared.admin.SetNbMaxMsgRequest;
import org.objectweb.joram.shared.admin.SetThresholdRequest;
import org.objectweb.joram.shared.excepts.AccessException;
import org.objectweb.joram.shared.excepts.DestinationException;
import org.objectweb.joram.shared.excepts.MomException;
import org.objectweb.joram.shared.selectors.Selector;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import fr.dyade.aaa.agent.AgentId;
import fr.dyade.aaa.agent.BagSerializer;
import fr.dyade.aaa.agent.Channel;
import fr.dyade.aaa.agent.DeleteNot;
import fr.dyade.aaa.agent.ExpiredNot;
import fr.dyade.aaa.agent.Notification;
import fr.dyade.aaa.agent.UnknownAgent;
import fr.dyade.aaa.common.Debug;
/**
* The <code>Queue</code> class implements the MOM queue behavior,
* basically storing messages and delivering them upon clients requests.
*/
public class Queue extends Destination implements QueueMBean, BagSerializer {
/** define serialVersionUID for interoperability */
private static final long serialVersionUID = 1L;
public static Logger logger = Debug.getLogger(Queue.class.getName());
/** Static value holding the default DMQ identifier for a server. */
static AgentId defaultDMQId = null;
/**
* Threshold above which messages are considered as undeliverable because
* constantly denied; 0 stands for no threshold, -1 for value not set.
*/
private int threshold = -1;
/** Static value holding the default threshold for a server. */
static int defaultThreshold = -1;
/**
* Returns the threshold value of this queue, -1 if not set.
*
* @return the threshold value of this queue; -1 if not set.
*/
public int getThreshold() {
return threshold;
}
/**
* Sets or unsets the threshold for this queue.
*
* @param threshold The threshold value to be set (-1 for unsetting previous value).
*/
public void setThreshold(int threshold) {
this.threshold = threshold;
}
/** Static method returning the default threshold. */
public static int getDefaultThreshold() {
return defaultThreshold;
}
/** Static method returning the default DMQ identifier. */
public static AgentId getDefaultDMQId() {
return defaultDMQId;
}
/** <code>true</code> if all the stored messages have the same priority. */
private boolean samePriorities;
/** Common priority value. */
private int priority;
/** Table keeping the messages' consumers identifiers. */
protected Map consumers = new Hashtable();
/** Table keeping the messages' consumers contexts. */
protected Map contexts = new Hashtable();
/** Counter of messages arrivals. */
protected long arrivalsCounter = 0;
/** List holding the requests before reply or expiry. */
protected List requests = new Vector();
/**
* Distributes the received notifications to the appropriate reactions.
*
* @throws Exception
*/
public void react(AgentId from, Notification not) throws Exception {
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, "Queue.react(" + from + ',' + not + ')');
try {
if (not instanceof ReceiveRequest)
receiveRequest(from, (ReceiveRequest) not);
else if (not instanceof BrowseRequest)
browseRequest(from, (BrowseRequest) not);
else if (not instanceof AcknowledgeRequest)
acknowledgeRequest((AcknowledgeRequest) not);
else if (not instanceof DenyRequest)
denyRequest(from, (DenyRequest) not);
else if (not instanceof AbortReceiveRequest)
abortReceiveRequest(from, (AbortReceiveRequest) not);
else if (not instanceof ExpiredNot)
handleExpiredNot(from, (ExpiredNot) not);
else
super.react(from, not);
} catch (MomException exc) {
// MOM Exceptions are sent to the requester.
if (logger.isLoggable(BasicLevel.WARN))
logger.log(BasicLevel.WARN, exc);
if (not instanceof AbstractRequestNot) {
AbstractRequestNot req = (AbstractRequestNot) not;
Channel.sendTo(from, new ExceptionReply(req, exc));
}
}
}
/**
* Removes all request that the expiration time is expired.
*/
public void cleanWaitingRequest() {
cleanWaitingRequest(System.currentTimeMillis());
}
/**
* Cleans the waiting request list.
* Removes all request that the expiration time is less than the time
* given in parameter.
*
* @param currentTime The current time.
*/
protected void cleanWaitingRequest(long currentTime) {
int index = 0;
while (index < requests.size()) {
if (! ((ReceiveRequest) requests.get(index)).isValid(currentTime)) {
// Request expired: removing it
requests.remove(index);
// It's not really necessary to save its state, in case of failure
// a similar work will be done at restart.
} else {
index++;
}
}
}
/**
* Returns the number of waiting requests in the queue.
*
* @return The number of waiting requests.
*/
public final int getWaitingRequestCount() {
if (requests != null) {
cleanWaitingRequest(System.currentTimeMillis());
return requests.size();
}
return 0;
}
/** <code>true</code> if the queue is currently receiving messages. */
protected transient boolean receiving = false;
/** List holding the messages before delivery. */
protected transient List messages;
/**
* Removes all messages that the time-to-live is expired.
*/
public void cleanPendingMessage() {
cleanPendingMessage(System.currentTimeMillis());
}
public byte getType() {
return DestinationConstants.QUEUE_TYPE;
}
/**
* Cleans the pending messages list. Removes all messages which expire before
* the date given in parameter.
*
* @param currentTime
* The current time.
* @return A <code>DMQManager</code> which contains the expired messages.
* <code>null</code> if there wasn't any.
*/
protected DMQManager cleanPendingMessage(long currentTime) {
int index = 0;
DMQManager dmqManager = null;
Message message = null;
while (index < messages.size()) {
message = (Message) messages.get(index);
if (! message.isValid(currentTime)) {
messages.remove(index);
if (dmqManager == null)
dmqManager = new DMQManager(dmqId, getId());
nbMsgsSentToDMQSinceCreation++;
message.delete();
dmqManager.addDeadMessage(message.getFullMessage(), MessageErrorConstants.EXPIRED);
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG,
"Removes expired message " + message.getIdentifier(), new Exception());
} else {
index++;
}
}
return dmqManager;
}
/**
* Returns the number of pending messages in the queue.
*
* @return The number of pending messages.
*/
public final int getPendingMessageCount() {
if (messages != null) {
return messages.size();
}
return 0;
}
/** Table holding the delivered messages before acknowledgment. */
protected transient Map deliveredMsgs;
/**
* Returns the number of messages delivered and waiting for acknowledge.
*
* @return The number of messages delivered.
*/
public final int getDeliveredMessageCount() {
if (deliveredMsgs != null) {
return deliveredMsgs.size();
}
return 0;
}
public final long getNbMsgsReceiveSinceCreation() {
return nbMsgsSentToDMQSinceCreation + nbMsgsDeliverSinceCreation + getPendingMessageCount();
}
/** nb Max of Message store in queue (-1 no limit). */
protected int nbMaxMsg = -1;
/**
* Returns the maximum number of message for the destination.
* If the limit is unset the method returns -1.
*
* @return the maximum number of message for subscription if set;
* -1 otherwise.
*/
public final int getNbMaxMsg() {
return nbMaxMsg;
}
/**
* Sets the maximum number of message for the destination.
*
* @param nbMaxMsg the maximum number of message (-1 set no limit).
*/
public void setNbMaxMsg(int nbMaxMsg) {
// state change, so save.
setSave();
this.nbMaxMsg = nbMaxMsg;
}
/**
* Initializes the destination.
*
* @param firstTime true when first called by the factory
*/
public void initialize(boolean firstTime) {
cleanWaitingRequest(System.currentTimeMillis());
receiving = false;
messages = new Vector();
deliveredMsgs = new Hashtable();
if (firstTime) return;
// Retrieving the persisted messages, if any.
List persistedMsgs = Message.loadAll(getMsgTxPrefix().toString());
if (persistedMsgs != null) {
Message persistedMsg;
AgentId consId;
while (! persistedMsgs.isEmpty()) {
persistedMsg = (Message) persistedMsgs.remove(0);
consId = (AgentId) consumers.get(persistedMsg.getIdentifier());
if (consId == null) {
if (!addMessage(persistedMsg)) {
persistedMsg.delete();
}
} else if (isLocal(consId)) {
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, " -> deny " + persistedMsg.getIdentifier());
consumers.remove(persistedMsg.getIdentifier());
contexts.remove(persistedMsg.getIdentifier());
if (!addMessage(persistedMsg)) {
persistedMsg.delete();
}
} else {
deliveredMsgs.put(persistedMsg.getIdentifier(), persistedMsg);
}
}
}
}
/**
* Returns a string representation of this destination.
*/
public String toString() {
return "Queue:" + getId().toString();
}
/**
* wake up, and cleans the queue.
*/
public void wakeUpNot(WakeUpNot not) {
long current = System.currentTimeMillis();
cleanWaitingRequest(current);
// Cleaning the possibly expired messages.
DMQManager dmqManager = cleanPendingMessage(current);
// If needed, sending the dead messages to the DMQ:
if (dmqManager != null)
dmqManager.sendToDMQ();
}
/**
* This method allows to exclude some JMX attribute of getJMXStatistics method.
* It excludes.
*
* @param attrName name of attribute to test.
* @return true if the attribute is a valid one.
*/
protected boolean isValidJMXAttribute(String attrName) {
if ("Messages".equals(attrName))
return false;
return super.isValidJMXAttribute(attrName);
}
/**
* Method implementing the reaction to a <code>ReceiveRequest</code>
* instance, requesting a message.
* <p>
* This method stores the request and launches a delivery sequence.
*
* @exception AccessException If the sender is not a reader.
*/
protected void receiveRequest(AgentId from, ReceiveRequest not) throws AccessException {
// If client is not a reader, sending an exception.
if (! isReader(from))
throw new AccessException("READ right not granted");
String[] toAck = not.getMessageIds();
if (toAck != null) {
for (int i = 0; i < toAck.length; i++) {
acknowledge(toAck[i]);
}
}
long current = System.currentTimeMillis();
cleanWaitingRequest(current);
// Storing the request:
not.requester = from;
not.setExpiration(current);
if (not.isPersistent()) {
// state change, so save.
setSave();
}
requests.add(not);
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, " -> requests count = " + requests.size());
// Launching a delivery sequence for this request:
int reqIndex = requests.size() - 1;
deliverMessages(reqIndex);
// If the request has not been answered and if it is an immediate
// delivery request, sending a null:
if ((requests.size() - 1) == reqIndex && not.getTimeOut() == -1) {
requests.remove(reqIndex);
QueueMsgReply reply = new QueueMsgReply(not);
if (isLocal(from)) {
reply.setPersistent(false);
}
forward(from, reply);
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, "Receive answered by a null.");
}
}
/**
* Method implementing the queue reaction to a <code>BrowseRequest</code>
* instance, requesting an enumeration of the messages on the queue.
* <p>
* The method sends a <code>BrowseReply</code> back to the client. Expired
* messages are sent to the DMQ.
*
* @exception AccessException If the requester is not a reader.
*/
protected void browseRequest(AgentId from, BrowseRequest not) throws AccessException {
// If client is not a reader, sending an exception.
if (! isReader(from))
throw new AccessException("READ right not granted");
// Building the reply:
BrowseReply rep = new BrowseReply(not);
// Cleaning the possibly expired messages.
DMQManager dmqManager = cleanPendingMessage(System.currentTimeMillis());
// Adding the deliverable messages to it:
Message message;
for (int i = 0; i < messages.size(); i++) {
message = (Message) messages.get(i);
if (Selector.matches(message.getHeaderMessage(), not.getSelector())) {
// Matching selector: adding the message:
rep.addMessage(message.getFullMessage());
}
}
// Sending the dead messages to the DMQ, if needed:
if (dmqManager != null)
dmqManager.sendToDMQ();
// Delivering the reply:
forward(from, rep);
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, "Request answered.");
}
/**
* Method implementing the reaction to an <code>AcknowledgeRequest</code>
* instance, requesting messages to be acknowledged.
*/
protected void acknowledgeRequest(AcknowledgeRequest not) {
for (Enumeration ids = not.getIds(); ids.hasMoreElements();) {
String msgId = (String) ids.nextElement();
acknowledge(msgId);
}
}
private void acknowledge(String msgId) {
Message msg = (Message) deliveredMsgs.remove(msgId);
if ((msg != null) && msg.isPersistent()) {
// state change, so save.
setSave();
}
consumers.remove(msgId);
contexts.remove(msgId);
if (msg != null) {
msg.delete();
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, "Message " + msgId + " acknowledged.");
} else if (logger.isLoggable(BasicLevel.WARN)) {
logger.log(BasicLevel.WARN,
"Message " + msgId + " not found for acknowledgement.");
}
}
/**
* Method implementing the reaction to a <code>DenyRequest</code>
* instance, requesting messages to be denied.
* <p>
* This method denies the messages and launches a delivery sequence.
* Messages considered as undeliverable are sent to the DMQ.
*/
protected void denyRequest(AgentId from, DenyRequest not) {
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, "Queue.DenyRequest(" + from + ',' + not + ')');
Enumeration ids = not.getIds();
String msgId;
Message message;
AgentId consId;
int consCtx;
DMQManager dmqManager = null;
if (! ids.hasMoreElements()) {
// If the deny request is empty, the denying is a contextual one: it
// requests the denying of all the messages consumed by the denier in
// the denying context:
for (Iterator entries = deliveredMsgs.entrySet().iterator(); entries.hasNext();) {
// Browsing the delivered messages:
Map.Entry entry = (Map.Entry) entries.next();
msgId = (String) entry.getKey();
message = (Message) entry.getValue();
consId = (AgentId) consumers.get(msgId);
consCtx = ((Integer) contexts.get(msgId)).intValue();
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, " -> deny msg " + msgId + "(consId = " + consId + ')');
// If the current message has been consumed by the denier in the same
// context: denying it.
if (consId.equals(from) && consCtx == not.getClientContext()) {
// state change, so save.
setSave();
consumers.remove(msgId);
contexts.remove(msgId);
entries.remove();
message.setRedelivered();
// If message considered as undeliverable, adding
// it to the list of dead messages:
if (isUndeliverable(message)) {
message.delete();
if (dmqManager == null)
dmqManager = new DMQManager(dmqId, getId());
nbMsgsSentToDMQSinceCreation++;
dmqManager.addDeadMessage(message.getFullMessage(), MessageErrorConstants.UNDELIVERABLE);
} else {
// Else, putting the message back into the deliverables list:
storeMessageHeader(message);
}
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, "Message " + msgId + " denied.");
}
}
}
// For a non empty request, browsing the denied messages:
for (ids = not.getIds(); ids.hasMoreElements();) {
msgId = (String) ids.nextElement();
message = (Message) deliveredMsgs.remove(msgId);
// Message may have already been denied. For example, a proxy may deny
// a message twice, first when detecting a connection failure - and
// in that case it sends a contextual denying -, then when receiving
// the message from the queue - and in that case it also sends an
// individual denying.
if (message == null) {
if (logger.isLoggable(BasicLevel.ERROR))
logger.log(BasicLevel.ERROR, " -> already denied message " + msgId);
break;
}
message.setRedelivered();
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, " -> deny " + msgId);
// state change, so save.
setSave();
consumers.remove(msgId);
contexts.remove(msgId);
// If message considered as undeliverable, adding it
// to the list of dead messages:
if (isUndeliverable(message)) {
message.delete();
if (dmqManager == null)
dmqManager = new DMQManager(dmqId, getId());
nbMsgsSentToDMQSinceCreation++;
dmqManager.addDeadMessage(message.getFullMessage(), MessageErrorConstants.UNDELIVERABLE);
} else {
// Else, putting the message back into the deliverables list:
storeMessageHeader(message);
}
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, "Message " + msgId + " denied.");
}
// Sending the dead messages to the DMQ, if needed:
if (dmqManager != null)
dmqManager.sendToDMQ();
// Launching a delivery sequence:
deliverMessages(0);
}
protected void abortReceiveRequest(AgentId from,
AbortReceiveRequest not) {
for (int i = 0; i < requests.size(); i++) {
ReceiveRequest request = (ReceiveRequest) requests.get(i);
if (request.requester.equals(from) &&
request.getClientContext() == not.getClientContext() &&
request.getRequestId() == not.getAbortedRequestId()) {
if (not.isPersistent()) {
// state change, so save.
setSave();
}
requests.remove(i);
break;
}
}
}
/**
* @see org.objectweb.joram.mom.dest.Destination#handleAdminRequestNot(fr.dyade.aaa.agent.AgentId, org.objectweb.joram.mom.notifications.FwdAdminRequestNot)
*/
public void handleAdminRequestNot(AgentId from, FwdAdminRequestNot not) {
AdminRequest adminRequest = not.getRequest();
if (adminRequest instanceof GetQueueMessageIds) {
getQueueMessageIds(not.getReplyTo(),
not.getRequestMsgId(),
not.getReplyMsgId());
} else if (adminRequest instanceof GetQueueMessage) {
getQueueMessage((GetQueueMessage)adminRequest,
not.getReplyTo(),
not.getRequestMsgId(),
not.getReplyMsgId());
} else if (adminRequest instanceof DeleteQueueMessage) {
deleteQueueMessage((DeleteQueueMessage)adminRequest,
not.getReplyTo(),
not.getRequestMsgId(),
not.getReplyMsgId());
} else if (adminRequest instanceof ClearQueue) {
clearQueue(not.getReplyTo(),
not.getRequestMsgId(),
not.getReplyMsgId());
} else if (adminRequest instanceof GetNbMaxMsgRequest) {
replyToTopic(new GetNumberReply(getNbMaxMsg()),
not.getReplyTo(),
not.getRequestMsgId(),
not.getReplyMsgId());
} else if (adminRequest instanceof GetPendingMessages) {
// Cleaning of the possibly expired messages.
DMQManager dmqManager = cleanPendingMessage(System.currentTimeMillis());
// Sending the dead messages to the DMQ, if needed:
if (dmqManager != null) dmqManager.sendToDMQ();
replyToTopic(new GetNumberReply(getPendingMessageCount()),
not.getReplyTo(),
not.getRequestMsgId(),
not.getReplyMsgId());
} else if (adminRequest instanceof GetPendingRequests) {
// Cleaning of the possibly expired requests.
cleanWaitingRequest(System.currentTimeMillis());
replyToTopic(new GetNumberReply(getWaitingRequestCount()),
not.getReplyTo(),
not.getRequestMsgId(),
not.getReplyMsgId());
} else if (adminRequest instanceof GetDMQSettingsRequest) {
replyToTopic(new GetDMQSettingsReply((dmqId != null)?dmqId.toString():null, threshold),
not.getReplyTo(),
not.getRequestMsgId(),
not.getReplyMsgId());
} else if (adminRequest instanceof SetThresholdRequest) {
setSave(); // state change, so save.
threshold = ((SetThresholdRequest) adminRequest).getThreshold();
replyToTopic(new AdminReply(true, null),
not.getReplyTo(),
not.getRequestMsgId(),
not.getReplyMsgId());
} else if (adminRequest instanceof SetNbMaxMsgRequest) {
setSave(); // state change, so save.
nbMaxMsg = ((SetNbMaxMsgRequest) adminRequest).getNbMaxMsg();
replyToTopic(new AdminReply(true, null),
not.getReplyTo(),
not.getRequestMsgId(),
not.getReplyMsgId());
} else {
super.handleAdminRequestNot(from, not);
}
}
private void getQueueMessageIds(AgentId replyTo,
String requestMsgId,
String replyMsgId) {
String[] res = new String[messages.size()];
for (int i = 0; i < messages.size(); i++) {
Message msg = (Message) messages.get(i);
res[i] = msg.getIdentifier();
}
replyToTopic(new GetQueueMessageIdsRep(res), replyTo, requestMsgId, replyMsgId);
}
private void getQueueMessage(GetQueueMessage request,
AgentId replyTo,
String requestMsgId,
String replyMsgId) {
Message message = null;
for (int i = 0; i < messages.size(); i++) {
message = (Message) messages.get(i);
if (message.getIdentifier().equals(request.getMessageId())) break;
message = null;
}
if (message != null) {
GetQueueMessageRep reply = null;
if (request.getFullMessage()) {
reply = new GetQueueMessageRep(message.getFullMessage());
} else {
reply = new GetQueueMessageRep(message.getHeaderMessage());
}
replyToTopic(reply, replyTo, requestMsgId, replyMsgId);
} else {
replyToTopic(new AdminReply(false, "Unknown message " + request.getMessageId()), replyTo, requestMsgId,
replyMsgId);
}
}
private void deleteQueueMessage(DeleteQueueMessage request,
AgentId replyTo,
String requestMsgId,
String replyMsgId) {
for (int i = 0; i < messages.size(); i++) {
Message message = (Message) messages.get(i);
if (message.getIdentifier().equals(request.getMessageId())) {
messages.remove(i);
message.delete();
DMQManager dmqManager = new DMQManager(dmqId, getId());
nbMsgsSentToDMQSinceCreation++;
dmqManager.addDeadMessage(message.getFullMessage(), MessageErrorConstants.ADMIN_DELETED);
dmqManager.sendToDMQ();
break;
}
}
replyToTopic(new AdminReply(true, null), replyTo, requestMsgId, replyMsgId);
}
private void clearQueue(AgentId replyTo,
String requestMsgId,
String replyMsgId) {
if (messages.size() > 0) {
DMQManager dmqManager = new DMQManager(dmqId, getId());
for (int i = 0; i < messages.size(); i++) {
Message message = (Message) messages.get(i);
message.delete();
nbMsgsSentToDMQSinceCreation++;
dmqManager.addDeadMessage(message.getFullMessage(), MessageErrorConstants.ADMIN_DELETED);
}
dmqManager.sendToDMQ();
messages.clear();
}
replyToTopic(new AdminReply(true, null), replyTo, requestMsgId, replyMsgId);
}
/**
* Method specifically processing a <code>SetRightRequest</code> instance.
* <p>
* When a reader is removed, and receive requests of this reader are still
* on the queue, they are replied to by an <code>ExceptionReply</code>.
*/
protected void doRightRequest(AgentId user, int right) {
// If the request does not unset a reader, doing nothing.
if (right != -READ) return;
ReceiveRequest request;
if (user == null) {
// Free reading right has been removed, reject the non readers requests.
for (int i = 0; i < requests.size(); i++) {
request = (ReceiveRequest) requests.get(i);
if (! isReader(request.requester)) {
forward(request.requester,
new ExceptionReply(request, new AccessException("Free READ access removed")));
setSave(); // state change, so save.
requests.remove(i);
i--;
}
}
} else {
// Reading right of a given user has been removed; replying to its requests.
for (int i = 0; i < requests.size(); i++) {
request = (ReceiveRequest) requests.get(i);
if (user.equals(request.requester)) {
forward(request.requester,
new ExceptionReply(request, new AccessException("READ right removed")));
setSave(); // state change, so save.
requests.remove(i);
i--;
}
}
}
}
/**
* Method specifically processing a <code>ClientMessages</code> instance.
* <p>
* This method stores the messages and launches a delivery sequence.
*/
protected void doClientMessages(AgentId from, ClientMessages not) {
receiving = true;
ClientMessages cm = null;
// interceptors
if (interceptorsAvailable()) {
// new client message
cm = new ClientMessages(not.getClientContext(), not.getRequestId());
cm.setAsyncSend(not.getAsyncSend());
cm.setDMQId(not.getDMQId());
for (Iterator msgs = not.getMessages().iterator(); msgs.hasNext();) {
org.objectweb.joram.shared.messages.Message message = (org.objectweb.joram.shared.messages.Message) msgs.next();
// set the destination name
message.setProperty("JoramDestinationName", getName());
// interceptors process
org.objectweb.joram.shared.messages.Message m = processInterceptors(message);
if (m == null) {
// send message to the DMQ
DMQManager dmqManager = new DMQManager(dmqId, getId());
nbMsgsSentToDMQSinceCreation++;
dmqManager.addDeadMessage(message, MessageErrorConstants.INTERCEPTORS);
dmqManager.sendToDMQ();
new Message(message).releaseFullMessage();
} else {
// add message to the client message
cm.addMessage(m);
}
}
// test client message size.
if (cm.getMessageCount() == 0) {
receiving = false;
return;
}
} else {
cm = not;
}
// pre process the client message
ClientMessages clientMsgs = preProcess(from, cm);
if (clientMsgs != null) {
Message msg;
// Storing each received message:
for (Iterator msgs = clientMsgs.getMessages().iterator(); msgs.hasNext();) {
msg = new Message((org.objectweb.joram.shared.messages.Message) msgs.next());
msg.order = arrivalsCounter++;
storeMessage(msg);
setSave();
}
}
// Launching a delivery sequence:
deliverMessages(0);
if (clientMsgs != null)
postProcess(clientMsgs);
receiving = false;
}
/**
* Method specifically processing an <code>UnknownAgent</code> instance.
* <p>
* The specific processing is done when a <code>QueueMsgReply</code> was
* sent to a requester which does not exist anymore. In that case, the
* messages sent to this requester and not yet acknowledged are marked as
* "denied" for delivery to an other requester, and a new delivery sequence
* is launched. Messages considered as undeliverable are removed and sent to
* the DMQ.
*/
protected void doUnknownAgent(UnknownAgent uA) {
AgentId client = uA.agent;
Notification not = uA.not;
// If the notification is not a delivery, doing nothing.
if (! (not instanceof QueueMsgReply))
return;
String msgId;
Message message;
AgentId consId;
DMQManager dmqManager = null;
for (Iterator entries = deliveredMsgs.entrySet().iterator(); entries.hasNext();) {
Map.Entry entry = (Map.Entry) entries.next();
msgId = (String) entry.getKey();
message = (Message) entry.getValue();
consId = (AgentId) consumers.get(msgId);
// Delivered message has been delivered to the deleted client:
// denying it.
if (consId.equals(client)) {
entries.remove();
message.setRedelivered();
// state change, so save.
setSave();
consumers.remove(msgId);
contexts.remove(msgId);
// If message considered as undeliverable, adding it to the
// list of dead messages:
if (isUndeliverable(message)) {
message.delete();
if (dmqManager == null)
dmqManager = new DMQManager(dmqId, getId());
nbMsgsSentToDMQSinceCreation++;
dmqManager.addDeadMessage(message.getFullMessage(), MessageErrorConstants.UNDELIVERABLE);
} else {
// Else, putting it back into the deliverables list:
storeMessageHeader(message);
}
if (logger.isLoggable(BasicLevel.WARN))
logger.log(BasicLevel.WARN,
"Message " + message.getIdentifier() + " denied.");
}
}
// Sending dead messages to the DMQ, if needed:
if (dmqManager != null)
dmqManager.sendToDMQ();
// Launching a delivery sequence:
deliverMessages(0);
}
/**
* Method specifically processing a
* <code>fr.dyade.aaa.agent.DeleteNot</code> instance.
* <p>
* <code>ExceptionReply</code> replies are sent to the pending receivers,
* and the remaining messages are sent to the DMQ and deleted.
*/
protected void doDeleteNot(DeleteNot not) {
// Building the exception to send to the pending receivers:
DestinationException exc = new DestinationException("Queue " + getId() + " is deleted.");
ReceiveRequest rec;
ExceptionReply excRep;
// Sending it to the pending receivers:
cleanWaitingRequest(System.currentTimeMillis());
for (int i = 0; i < requests.size(); i++) {
rec = (ReceiveRequest) requests.get(i);
excRep = new ExceptionReply(rec, exc);
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG,
"Requester " + rec.requester + " notified of the queue deletion.");
forward(rec.requester, excRep);
}
// Sending the remaining messages to the DMQ, if needed:
if (! messages.isEmpty()) {
Message message;
DMQManager dmqManager = new DMQManager(dmqId, getId());
while (! messages.isEmpty()) {
message = (Message) messages.remove(0);
message.delete();
nbMsgsSentToDMQSinceCreation++;
dmqManager.addDeadMessage(message.getFullMessage(), MessageErrorConstants.DELETED_DEST);
}
dmqManager.sendToDMQ();
}
// Deleting the messages:
Message.deleteAll(getMsgTxPrefix().toString());
}
transient StringBuffer msgTxPrefix = null;
transient int msgTxPrefixLength = 0;
protected final StringBuffer getMsgTxPrefix() {
if (msgTxPrefix == null) {
msgTxPrefix = new StringBuffer(18).append('M').append(getId().toString()).append('_');
msgTxPrefixLength = msgTxPrefix.length();
}
return msgTxPrefix;
}
protected final void setMsgTxName(Message msg) {
if (msg.getTxName() == null) {
msg.setTxName(getMsgTxPrefix().append(msg.order).toString());
msgTxPrefix.setLength(msgTxPrefixLength);
}
}
/**
* Actually stores a message in the deliverables list.
*
* @param msg The message to store.
*/
protected final synchronized void storeMessage(Message msg) {
if (addMessage(msg)) {
// Persisting the message.
setMsgTxName(msg);
msg.save();
msg.releaseFullMessage();
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, "Message " + msg.getIdentifier() + " stored.");
}
}
/**
* Actually stores a message header in the deliverables list.
*
* @param message
* The message to store.
*/
protected final synchronized void storeMessageHeader(Message message) {
if (addMessage(message)) {
// Persisting the message.
message.saveHeader();
message.releaseFullMessage();
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, "Message " + message.getIdentifier() + " stored.");
}
}
/**
* Adds a message in the list of messages to deliver.
*
* @param message
* the message to add.
* @return true if the message has been added. false if the queue is full.
*/
protected final synchronized boolean addMessage(Message message) {
if (nbMaxMsg > -1 && nbMaxMsg <= messages.size()) {
DMQManager dmqManager = new DMQManager(dmqId, getId());
nbMsgsSentToDMQSinceCreation++;
dmqManager.addDeadMessage(message.getFullMessage(), MessageErrorConstants.QUEUE_FULL);
dmqManager.sendToDMQ();
return false;
}
if (messages.isEmpty()) {
samePriorities = true;
priority = message.getPriority();
} else if (samePriorities && priority != message.getPriority()) {
samePriorities = false;
}
if (samePriorities) {
// Constant priorities: no need to insert the message according to
// its priority.
if (receiving) {
// Message being received: adding it at the end of the queue.
messages.add(message);
} else {
// Denying or recovery: adding the message according to its original
// arrival order.
long currentO;
int i = 0;
for (Iterator ite = messages.iterator(); ite.hasNext();) {
currentO = ((Message) ite.next()).order;
if (currentO > message.order) break;
i++;
}
messages.add(i, message);
}
} else {
// Non constant priorities: inserting the message according to its
// priority.
Message currentMsg;
int currentP;
long currentO;
int i = 0;
for (Iterator ite = messages.iterator(); ite.hasNext();) {
currentMsg = (Message) ite.next();
currentP = currentMsg.getPriority();
currentO = currentMsg.order;
if (! receiving && currentP == message.getPriority()) {
// Message denied or recovered, priorities are equal: inserting the
// message according to its original arrival order.
if (currentO > message.order) break;
} else if (currentP < message.getPriority()) {
// Current priority lower than the message to store: inserting it.
break;
}
i++;
}
messages.add(i, message);
}
return true;
}
/**
* Get a client message contain <code>nb</code> messages.
*
* @param nb number of messages returned in ClientMessage.
* @param selector jms selector
* @param remove delete all messages returned if true
* @return ClientMessages (contains nb Messages)
*/
protected ClientMessages getClientMessages(int nb, String selector, boolean remove) {
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, "Queue.getClientMessages(" + nb + ',' + selector + ',' + remove + ')');
ClientMessages cm = null ;
List lsMessages = getMessages(nb, selector, remove);
if (lsMessages.size() > 0) {
cm = new ClientMessages();
}
Message message = null;
Iterator itMessages = lsMessages.iterator();
while (itMessages.hasNext()) {
message = (Message) itMessages.next();
cm.addMessage(message.getFullMessage());
}
return cm;
}
// /**
// * List of message to be removed.
// *
// * @param msgIds List of message id.
// */
// protected void removeMessages(List msgIds) {
// String id = null;
// Iterator itMessages = msgIds.iterator();
// while (itMessages.hasNext()) {
// id = (String) itMessages.next();
// int i = 0;
// Message message = null;
// while (i < messages.size()) {
// message = (Message) messages.get(i);
// if (id.equals(message.getIdentifier())) {
// messages.remove(i);
// message.delete();
// break;
// }
// }
// }
// }
/**
* get messages, if it's possible.
*
* @param nb
* -1 return all messages.
* @param selector
* jms selector.
* @return List of mom messages.
*/
private List getMessages(int nb, String selector, boolean remove) {
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, "Queue.getMessages(" + nb + ',' + selector + ',' + remove + ')');
List lsMessages = new ArrayList();
Message message;
int j = 0;
// Checking the deliverable messages:
while ((lsMessages.size() < nb || nb == -1) && j < messages.size()) {
message = (Message) messages.get(j);
// If selector matches, sending the message:
if (Selector.matches(message.getHeaderMessage(), selector) &&
checkDelivery(message.getHeaderMessage())) {
message.incDeliveryCount();
nbMsgsDeliverSinceCreation++;
// use in sub class see ClusterQueue
messageDelivered(message.getIdentifier());
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, "Message " + message.getIdentifier());
lsMessages.add(message);
if (remove) {
messages.remove(message);
message.delete();
} else {
// message not remove: going on.
j++;
}
} else {
// If message delivered or selector does not match: going on
j++;
}
}
return lsMessages;
}
private Message getMomMessage(String msgId) {
Message msg = null;
for (Iterator ite = messages.iterator(); ite.hasNext();) {
msg = (Message) ite.next();
if (msgId.equals(msg.getIdentifier()))
return msg;
}
return msg;
}
/**
* get mom message, delete if remove = true.
*
* @param msgId message identification
* @param remove if true delete message
* @return mom message
*/
protected Message getQueueMessage(String msgId, boolean remove) {
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, "Queue.getMessage(" + msgId + ',' + remove + ')');
Message message = getMomMessage(msgId);
if (checkDelivery(message.getHeaderMessage())) {
message.incDeliveryCount();
nbMsgsDeliverSinceCreation++;
// use in sub class see ClusterQueue
messageDelivered(message.getIdentifier());
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, "Message " + msgId);
if (remove) {
messages.remove(message);
message.delete();
}
}
return message;
}
/**
* Returns the description of a particular pending message. The message is
* pointed out through its unique identifier.
*
* @param msgId The unique message's identifier.
* @return the description of the message.
*
* @see org.objectweb.joram.mom.messages.MessageJMXWrapper
*/
public CompositeData getMessage(String msgId) throws Exception {
Message msg = getQueueMessage(msgId, false);
if (msg == null) return null;
return MessageJMXWrapper.createCompositeDataSupport(msg);
}
/**
* Returns the description of all pending messages.
*
* @return the description of the message.
*
* @see org.objectweb.joram.mom.messages.MessageJMXWrapper
*/
public TabularData getMessages() throws Exception {
return MessageJMXWrapper.createTabularDataSupport(messages);
}
public List getMessagesView() {
return messages;
}
/**
* Actually tries to answer the pending "receive" requests.
* <p>
* The method may send <code>QueueMsgReply</code> replies to clients.
*
* @param index Index where starting to "browse" the requests.
*/
protected void deliverMessages(int index) {
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, "Queue.deliverMessages(" + index + ')');
ReceiveRequest notRec = null;
Message message;
QueueMsgReply notMsg;
List lsMessages = null;
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, " -> requests = " + requests + ')');
long current = System.currentTimeMillis();
cleanWaitingRequest(current);
// Cleaning the possibly expired messages.
DMQManager dmqManager = cleanPendingMessage(current);
// Processing each request as long as there are deliverable messages:
while (! messages.isEmpty() && index < requests.size()) {
notRec = (ReceiveRequest) requests.get(index);
notMsg = new QueueMsgReply(notRec);
lsMessages = getMessages(notRec.getMessageCount(), notRec.getSelector(), notRec.getAutoAck());
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, "Queue.deliverMessages: notRec.getAutoAck() = " + notRec.getAutoAck()
+ ", lsMessages = " + lsMessages);
Iterator itMessages = lsMessages.iterator();
while (itMessages.hasNext()) {
message = (Message) itMessages.next();
notMsg.addMessage(message.getFullMessage());
if (!notRec.getAutoAck()) {
// putting the message in the delivered messages table:
consumers.put(message.getIdentifier(), notRec.requester);
contexts.put(message.getIdentifier(),
new Integer(notRec.getClientContext()));
deliveredMsgs.put(message.getIdentifier(), message);
messages.remove(message);
}
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG,
"Message " + message.getIdentifier() + " to " + notRec.requester +
" as reply to " + notRec.getRequestId());
}
if (isLocal(notRec.requester)) {
notMsg.setPersistent(false);
}
if (notMsg.isPersistent() && !notRec.getAutoAck()) {
// state change, so save.
setSave();
}
// Next request:
if (notMsg.getSize() > 0) {
requests.remove(index);
forward(notRec.requester, notMsg);
} else {
index++;
}
}
// If needed, sending the dead messages to the DMQ:
if (dmqManager != null)
dmqManager.sendToDMQ();
}
/**
* Returns true if conditions are ok to deliver the message.
* This method must be overloaded in subclasses.
* Be careful only the message header is accessible.
*/
protected boolean checkDelivery(org.objectweb.joram.shared.messages.Message msg) {
return true;
}
/**
* call in deliverMessages just after forward(msg),
* overload this method to process a specific treatment.
*/
protected void messageDelivered(String msgId) {}
/**
* call in deliverMessages just after a remove message (invalid),
* overload this method to process a specific treatment.
*/
protected void messageRemoved(String msgId) {}
/**
* Returns <code>true</code> if a given message is considered as undeliverable,
* because its delivery count matches the queue's threshold, if any, or the
* server's default threshold value (if any).
*/
protected boolean isUndeliverable(Message message) {
if (threshold == 0) return false;
if (threshold > 0)
return (message.getDeliveryCount() >= threshold);
else if (Queue.getDefaultThreshold() > 0)
return (message.getDeliveryCount() >= Queue.getDefaultThreshold());
return false;
}
/**
* Adds the client messages in the queue.
*
* @param clientMsgs client message notification.
*/
public void addClientMessages(ClientMessages clientMsgs) {
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, "Queue.storeClientMessage(" + clientMsgs + ')');
if (clientMsgs != null) {
Message msg;
// Storing each received message:
for (Iterator msgs = clientMsgs.getMessages().iterator(); msgs.hasNext();) {
msg = new Message((org.objectweb.joram.shared.messages.Message) msgs.next());
msg.order = arrivalsCounter++;
if (interceptorsAvailable()) {
// get the shared message
org.objectweb.joram.shared.messages.Message message = msg.getFullMessage();
// set the destination name
message.setProperty("JoramDestinationName", getName());
// interceptors process
org.objectweb.joram.shared.messages.Message m = processInterceptors(message);
if (m == null) {
// send message to the DMQ
DMQManager dmqManager = new DMQManager(dmqId, getId());
nbMsgsSentToDMQSinceCreation++;
dmqManager.addDeadMessage(msg.getFullMessage(), MessageErrorConstants.INTERCEPTORS);
dmqManager.sendToDMQ();
msg.releaseFullMessage();
continue;
} else {
msg = new org.objectweb.joram.mom.messages.Message(m);
}
}
// store message
storeMessage(msg);
}
}
// Launching a delivery sequence:
deliverMessages(0);
}
public void readBag(ObjectInputStream in) throws IOException, ClassNotFoundException {
receiving = in.readBoolean();
messages = (List) in.readObject();
deliveredMsgs = (Map) in.readObject();
for (int i = 0; i < messages.size(); i++) {
Message message = (Message) messages.get(i);
// Persisting the message.
setMsgTxName(message);
message.save();
}
}
public void writeBag(ObjectOutputStream out) throws IOException {
out.writeBoolean(receiving);
out.writeObject(messages);
out.writeObject(deliveredMsgs);
}
protected void handleExpiredNot(AgentId from, ExpiredNot not) {
Notification expiredNot = not.getExpiredNot();
List messages;
// ClientMessages and TopicMsgsReply are the notifications which can expire in the networks.
// QueueMsgReply can't expire due to protocol limitations
if (expiredNot instanceof ClientMessages) {
messages = ((ClientMessages) expiredNot).getMessages();
} else if (expiredNot instanceof TopicMsgsReply) {
messages = ((TopicMsgsReply) expiredNot).getMessages();
} else {
if (logger.isLoggable(BasicLevel.ERROR))
logger.log(BasicLevel.ERROR,
"Expired notification holds an unknown notification: " + expiredNot.getClass().getName());
return;
}
// Let senderId to null because we want to explicitly send messages to the queue itself.
DMQManager dmqManager = new DMQManager(getId(), null);
Iterator iterator = messages.iterator();
while (iterator.hasNext()) {
dmqManager.addDeadMessage((org.objectweb.joram.shared.messages.Message) iterator.next(),
MessageErrorConstants.EXPIRED);
}
dmqManager.sendToDMQ();
}
}