/*
* Copyright 1999-2004 The Apache Software Foundation.
*
* Licensed 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.apache.sandesha2.util;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axiom.soap.SOAPFactory;
import org.apache.axis2.AxisFault;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.context.OperationContext;
import org.apache.axis2.context.OperationContextFactory;
import org.apache.axis2.description.AxisOperation;
import org.apache.axis2.engine.AxisEngine;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.sandesha2.RMMsgContext;
import org.apache.sandesha2.Sandesha2Constants;
import org.apache.sandesha2.SandeshaException;
import org.apache.sandesha2.i18n.SandeshaMessageHelper;
import org.apache.sandesha2.i18n.SandeshaMessageKeys;
import org.apache.sandesha2.storage.StorageManager;
import org.apache.sandesha2.storage.Transaction;
import org.apache.sandesha2.storage.beanmanagers.SenderBeanMgr;
import org.apache.sandesha2.storage.beans.RMDBean;
import org.apache.sandesha2.storage.beans.SenderBean;
/**
* Contains logic for managing acknowledgements.
*/
public class AcknowledgementManager {
private static Log log = LogFactory.getLog(AcknowledgementManager.class);
/**
* Piggybacks any available acks of the same sequence to the given
* application message.
*
* @param applicationRMMsgContext
* @throws SandeshaException
*/
public static Transaction piggybackAcksIfPresent(RMMsgContext rmMessageContext, StorageManager storageManager, Transaction transaction)
throws SandeshaException {
if (log.isDebugEnabled())
log.debug("Enter: AcknowledgementManager::piggybackAcksIfPresent");
SenderBeanMgr retransmitterBeanMgr = storageManager.getSenderBeanMgr();
// If this message is going to an anonymous address, and the inbound sequence has
// anonymous acksTo, then we add in an ack for the inbound sequence.
EndpointReference target = rmMessageContext.getTo();
if(target == null || target.hasAnonymousAddress()) {
if(target != null && SandeshaUtil.isWSRMAnonymous(target.getAddress())) {
// Search for any sequences that have an acksTo that matches this target, and add an ack
// for each of them.
RMDBean findBean = new RMDBean();
findBean.setAcksToEPR(target.getAddress());
findBean.setTerminated(false);
Collection rmdBeans = storageManager.getRMDBeanMgr().find(findBean);
Iterator sequences = rmdBeans.iterator();
while(sequences.hasNext()) {
RMDBean sequence = (RMDBean) sequences.next();
if (sequence.getHighestInMessageNumber() > 0) {
if(log.isDebugEnabled()) log.debug("Piggybacking ack for sequence: " + sequence.getSequenceID());
RMMsgCreator.addAckMessage(rmMessageContext, sequence.getSequenceID(), sequence);
}
}
} else {
// We have no good indicator of the identity of the destination, so the only sequence
// we can ack is the inbound one that caused us to create this response.
String inboundSequence = (String) rmMessageContext.getProperty(Sandesha2Constants.MessageContextProperties.INBOUND_SEQUENCE_ID);
if(inboundSequence != null) {
RMDBean inboundBean = SandeshaUtil.getRMDBeanFromSequenceId(storageManager, inboundSequence);
if(inboundBean != null && !inboundBean.isTerminated()) {
String acksTo = inboundBean.getAcksToEPR();
EndpointReference acksToEPR = new EndpointReference(acksTo);
if(acksTo == null || acksToEPR.hasAnonymousAddress()) {
if(log.isDebugEnabled()) log.debug("Piggybacking ack for inbound sequence: " + inboundSequence);
RMMsgCreator.addAckMessage(rmMessageContext, inboundSequence, inboundBean);
}
}
}
}
if(log.isDebugEnabled()) log.debug("Exit: AcknowledgementManager::piggybackAcksIfPresent, anon");
return transaction;
}
// From here on, we must be dealing with a real address. Piggyback all sequences that have an
// acksTo that matches the To address, and that have an ackMessage queued up for sending.
Set acked = new HashSet();
SenderBean findBean = new SenderBean();
findBean.setMessageType(Sandesha2Constants.MessageTypes.ACK);
findBean.setSend(true);
findBean.setToAddress(target.getAddress());
Collection collection = retransmitterBeanMgr.find(findBean);
if (transaction != null && transaction.isActive())
transaction.commit();
transaction = storageManager.getTransaction();
Iterator it = collection.iterator();
while (it.hasNext()) {
SenderBean ackBean = (SenderBean) it.next();
// Piggybacking will happen only if the end of ack interval (timeToSend) is not reached.
long timeNow = System.currentTimeMillis();
if (ackBean.getTimeToSend() > timeNow) {
// Delete the beans that would have sent the ack
retransmitterBeanMgr.delete(ackBean.getMessageID());
storageManager.removeMessageContext(ackBean.getMessageContextRefKey());
String sequenceId = ackBean.getSequenceID();
if (log.isDebugEnabled()) log.debug("Piggybacking ack for sequence: " + sequenceId);
RMDBean rmdBean = SandeshaUtil.getRMDBeanFromSequenceId(storageManager, sequenceId);
if(rmdBean != null && !rmdBean.isTerminated()) {
RMMsgCreator.addAckMessage(rmMessageContext, sequenceId, rmdBean);
}
acked.add(sequenceId);
}
}
// As a special case, if this is a terminate sequence message then add in ack messages for
// any sequences that have an acksTo that matches the target address. This helps to ensure
// that request-response sequence pairs end cleanly.
if(rmMessageContext.getMessageType() == Sandesha2Constants.MessageTypes.TERMINATE_SEQ) {
if(log.isDebugEnabled()) log.debug("Adding extra acks, as this is a terminate");
RMDBean findRMDBean = new RMDBean();
findRMDBean.setAcksToEPR(target.getAddress());
findRMDBean.setTerminated(false);
Collection rmdBeans = storageManager.getRMDBeanMgr().find(findRMDBean);
Iterator sequences = rmdBeans.iterator();
while(sequences.hasNext()) {
RMDBean sequence = (RMDBean) sequences.next();
String sequenceId = sequence.getSequenceID();
if(!acked.contains(sequenceId) && sequence.getHighestInMessageNumber() > 0) {
if(log.isDebugEnabled()) log.debug("Piggybacking ack for sequence: " + sequenceId);
RMMsgCreator.addAckMessage(rmMessageContext, sequenceId, sequence);
acked.add(sequenceId);
}
}
}
if (log.isDebugEnabled())
log.debug("Exit: AcknowledgementManager::piggybackAcksIfPresent");
return transaction;
}
/**
*
* @param referenceRMMessage
* @param sequencePropertyKey
* @param sequenceId
* @param storageManager
* @param makeResponse Some work will be done to make the new ack message the response of the reference message.
* @return
* @throws AxisFault
*/
public static RMMsgContext generateAckMessage(
RMMsgContext referenceRMMessage,
RMDBean rmdBean,
String sequenceId,
StorageManager storageManager,
boolean serverSide
) throws AxisFault {
if (log.isDebugEnabled())
log.debug("Enter: AcknowledgementManager::generateAckMessage " + rmdBean);
MessageContext referenceMsg = referenceRMMessage.getMessageContext();
EndpointReference acksTo = new EndpointReference(rmdBean.getAcksToEPR());
if (acksTo.getAddress() == null)
throw new SandeshaException(SandeshaMessageHelper.getMessage(SandeshaMessageKeys.acksToStrNotSet));
AxisOperation ackOperation = SpecSpecificConstants.getWSRMOperation(
Sandesha2Constants.MessageTypes.ACK,
rmdBean.getRMVersion(),
referenceMsg.getAxisService());
MessageContext ackMsgCtx = SandeshaUtil.createNewRelatedMessageContext(referenceRMMessage, ackOperation);
ackMsgCtx.setProperty(Sandesha2Constants.APPLICATION_PROCESSING_DONE, "true");
RMMsgContext ackRMMsgCtx = MsgInitializer.initializeMessage(ackMsgCtx);
ackRMMsgCtx.setFlow(MessageContext.OUT_FLOW);
ackRMMsgCtx.setRMNamespaceValue(referenceRMMessage.getRMNamespaceValue());
ackMsgCtx.setMessageID(SandeshaUtil.getUUID());
SOAPFactory factory = SOAPAbstractFactory.getSOAPFactory(SandeshaUtil
.getSOAPVersion(referenceMsg.getEnvelope()));
// Setting new envelope
SOAPEnvelope envelope = factory.getDefaultEnvelope();
ackMsgCtx.setEnvelope(envelope);
ackMsgCtx.setTo(acksTo);
ackMsgCtx.setServerSide(serverSide);
// adding the SequenceAcknowledgement part.
RMMsgCreator.addAckMessage(ackRMMsgCtx, sequenceId, rmdBean);
if (log.isDebugEnabled())
log.debug("Exit: AcknowledgementManager::generateAckMessage");
return ackRMMsgCtx;
}
public static boolean verifySequenceCompletion(RangeString ackRanges, long lastMessageNo) {
if (log.isDebugEnabled())
log.debug("Enter: AcknowledgementManager::verifySequenceCompletion");
boolean result = false;
Range complete = new Range(1, lastMessageNo);
if(ackRanges.isRangeCompleted(complete)) {
result = true;
}
if (log.isDebugEnabled())
log.debug("Exit: AcknowledgementManager::verifySequenceCompletion " + result);
return result;
}
public static void addAckBeanEntry (
RMMsgContext ackRMMsgContext,
String sequenceId,
long timeToSend,
StorageManager storageManager) throws AxisFault {
if(log.isDebugEnabled()) log.debug("Enter: AcknowledgementManager::addAckBeanEntry");
// Write the acks into the envelope
ackRMMsgContext.addSOAPEnvelope();
MessageContext ackMsgContext = ackRMMsgContext.getMessageContext();
SenderBeanMgr retransmitterBeanMgr = storageManager.getSenderBeanMgr();
String key = SandeshaUtil.getUUID();
SenderBean ackBean = new SenderBean();
ackBean.setMessageContextRefKey(key);
ackBean.setMessageID(ackMsgContext.getMessageID());
ackBean.setReSend(false);
ackBean.setSequenceID(sequenceId);
EndpointReference to = ackMsgContext.getTo();
if (to!=null)
ackBean.setToAddress(to.getAddress());
ackBean.setSend(true);
ackMsgContext.setProperty(Sandesha2Constants.QUALIFIED_FOR_SENDING, Sandesha2Constants.VALUE_FALSE);
ackBean.setMessageType(Sandesha2Constants.MessageTypes.ACK);
// removing old acks.
SenderBean findBean = new SenderBean();
findBean.setMessageType(Sandesha2Constants.MessageTypes.ACK);
findBean.setSend(true);
findBean.setReSend(false);
findBean.setSequenceID(sequenceId);
Collection coll = retransmitterBeanMgr.find(findBean);
Iterator it = coll.iterator();
while(it.hasNext()) {
SenderBean oldAckBean = (SenderBean) it.next();
if(oldAckBean.getTimeToSend() < timeToSend)
timeToSend = oldAckBean.getTimeToSend();
// removing the retransmitted entry for the oldAck
retransmitterBeanMgr.delete(oldAckBean.getMessageID());
// removing the message store entry for the old ack
storageManager.removeMessageContext(oldAckBean.getMessageContextRefKey());
}
ackBean.setTimeToSend(timeToSend);
ackMsgContext.setProperty(Sandesha2Constants.QUALIFIED_FOR_SENDING, Sandesha2Constants.VALUE_FALSE);
// passing the message through sandesha2sender
ackMsgContext.setProperty(Sandesha2Constants.SET_SEND_TO_TRUE, Sandesha2Constants.VALUE_TRUE);
SandeshaUtil.executeAndStore(ackRMMsgContext, key);
// inserting the new ack.
retransmitterBeanMgr.insert(ackBean);
if(log.isDebugEnabled()) log.debug("Exit: AcknowledgementManager::addAckBeanEntry");
}
public static void sendAckNow (RMMsgContext ackRMMsgContext) throws AxisFault {
if (log.isDebugEnabled())
log.debug("Enter: AcknowledgementManager::sendAckNow");
// Write the acks into the envelope
ackRMMsgContext.addSOAPEnvelope();
MessageContext ackMsgContext = ackRMMsgContext.getMessageContext();
// setting CONTEXT_WRITTEN since acksto is anonymous
if (ackRMMsgContext.getMessageContext().getOperationContext() == null) {
// operation context will be null when doing in a GLOBAL
// handler.
AxisOperation op = ackMsgContext.getAxisOperation();
OperationContext opCtx = OperationContextFactory.createOperationContext(op.getAxisSpecificMEPConstant(), op, ackRMMsgContext.getMessageContext().getServiceContext());
ackRMMsgContext.getMessageContext().setOperationContext(opCtx);
}
ackRMMsgContext.getMessageContext().setServerSide(true);
AxisEngine.send(ackMsgContext);
if (log.isDebugEnabled())
log.debug("Exit: AcknowledgementManager::sendAckNow");
}
}