/** * Copyright (c) 2005-2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * WSO2 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.apache.synapse.message.processor.impl.failover; import java.util.Date; import java.util.Map; import java.util.concurrent.TimeUnit; import org.apache.axiom.om.OMElement; import org.apache.axiom.soap.SOAPEnvelope; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.synapse.ManagedLifecycle; import org.apache.synapse.Mediator; import org.apache.synapse.MessageContext; import org.apache.synapse.commons.json.JsonUtil; import org.apache.synapse.core.SynapseEnvironment; import org.apache.synapse.core.axis2.Axis2MessageContext; import org.apache.synapse.message.MessageConsumer; import org.apache.synapse.message.MessageProducer; import org.apache.synapse.message.processor.MessageProcessor; import org.apache.synapse.message.processor.MessageProcessorConstants; import org.apache.synapse.message.store.MessageStore; import org.apache.synapse.task.Task; import org.apache.synapse.util.MessageHelper; /** * This task is responsible for forwarding a request to a given message store. */ public class FailoverForwardingService implements Task, ManagedLifecycle { private static final Log log = LogFactory.getLog(FailoverForwardingService.class); // The consumer that is associated with the particular message store private MessageConsumer messageConsumer; // Target message store producer private MessageProducer targetMessageProducer; // Target message store name private String targetMessageStoreName; // Owner of the this job private MessageProcessor messageProcessor; /* * Interval between two retries to the client. This only come to affect only * if the client is un-reachable */ private int retryInterval = 1000; // Sequence to invoke in a failure private String faultSeq = null; // Sequence to invoke in a message processor deactivation private String deactivateSeq = null; /* * The cron expression under which the message processor runs. */ private String cronExpression = null; /* * These two maintain the state of service. For each iteration these should * be reset */ private boolean isSuccessful = false; private volatile boolean isTerminated = false; /* * Number of retries before shutting-down the processor. -1 default value * indicates that * retry should happen forever */ private int maxDeliverAttempts = -1; private int attemptCount = 0; private boolean isThrottling = true; /** * Throttling-interval is the forwarding interval when cron scheduling is enabled. */ private long throttlingInterval = -1; // Message Queue polling interval value. private long interval; /* * Configuration to continue the message processor even without stopping * the message processor after maximum number of delivery */ private boolean isMaxDeliveryAttemptDropEnabled = false; /** * If false, the MessageProcessor will process every single message in the queue regardless of their origin * If true, it will only process messages that were processed by a MessageStore running on the same server * Default value is set to true */ private SynapseEnvironment synapseEnvironment; private boolean initialized = false; /** * Specifies whether the service should be started as deactivated or not */ private boolean isDeactivatedAtStartup = false; public FailoverForwardingService(MessageProcessor messageProcessor, SynapseEnvironment synapseEnvironment, long threshouldInterval, boolean isDeactivatedAtStartup) { this.messageProcessor = messageProcessor; this.synapseEnvironment = synapseEnvironment; this.interval = threshouldInterval; this.isDeactivatedAtStartup = isDeactivatedAtStartup; } /** * Starts the execution of this task which grabs a message from the message * queue and dispatch it to a given endpoint. */ public void execute() { final long startTime = new Date().getTime(); if (isDeactivatedAtStartup) { //This delay is required until tasks are paused from ScheduledMessageProcessor since message processor is // inactive try { TimeUnit.MILLISECONDS.sleep(MessageProcessorConstants.INITIAL_EXECUTION_DELAY); } catch (InterruptedException exception) { log.warn("Initial delay interrupted when Failover Forwarding service started as inactive ", exception); } isDeactivatedAtStartup = false; } /* * Initialize only if it is NOT already done. This will make sure that * the initialization is done only once. */ if (!initialized) { this.init(synapseEnvironment); } do { resetService(); MessageContext messageContext = null; try { if (!this.messageProcessor.isDeactivated()) { messageContext = fetch(messageConsumer); if (messageContext != null) { // Now it is NOT terminated anymore. isTerminated = messageProcessor.isDeactivated(); dispatch(messageContext); } else { // either the connection is broken or there are no new // massages. if (log.isDebugEnabled()) { log.debug("No messages were received for message processor [" + messageProcessor.getName() + "]"); } // this means we have consumed all the messages if (isRunningUnderCronExpression()) { break; } } } else { /* * we need this because when start the server while the * processors in deactivated mode * the deactivation may not come in to play because the * service may not be running. */ isTerminated = true; if (log.isDebugEnabled()) { log.debug("Exiting service since the message processor is deactivated"); } } } catch (Throwable e) { /* * All the possible recoverable exceptions are handles case by * case and yet if it comes this * we have to shutdown the processor */ log.fatal("Deactivating the message processor [" + this.messageProcessor.getName() + "]", e); deactivateMessageProcessor(messageContext); } if (log.isDebugEnabled()) { log.debug("Exiting the iteration of message processor [" + this.messageProcessor.getName() + "]"); } /* * This code wrote handle scenarios in which cron expressions are * used for scheduling task */ if (isRunningUnderCronExpression()) { try { Thread.sleep(throttlingInterval); } catch (InterruptedException e) { // no need to worry. it does have any serious consequences log.debug("Current Thread was interrupted while it is sleeping."); } } /* * If the interval is less than 1000 ms, then the scheduling is done * using the while loop since ntask rejects any intervals whose * value is less then 1000 ms. */ if (interval > 0 && interval < MessageProcessorConstants.THRESHOULD_INTERVAL) { try { Thread.sleep(interval); } catch (InterruptedException e) { log.debug("Current Thread was interrupted while it is sleeping."); } } /* * Gives the control back to Quartz scheduler. This needs to be done * only if the interval value is less than the Threshould interval * value of 1000 ms, where the scheduling is done outside of Quartz * via the while loop. Otherwise the schedular will get blocked. * For cron expressions this scenario is already * handled above. */ if (isThrottling && new Date().getTime() - startTime > 1000) { break; } } while ((isThrottling || isRunningUnderCronExpression()) && !isTerminated); if (log.isDebugEnabled()) { log.debug("Exiting service thread of message processor [" + this.messageProcessor.getName() + "]"); } } public void init(SynapseEnvironment se) { // Setting up the JMS consumer/Producer here. setMessageConsumerAndProducer(); // Defaults to -1. Map<String, Object> parametersMap = messageProcessor.getParameters(); if (parametersMap.get(MessageProcessorConstants.MAX_DELIVER_ATTEMPTS) != null) { maxDeliverAttempts = Integer.parseInt((String) parametersMap.get(MessageProcessorConstants.MAX_DELIVER_ATTEMPTS)); } if (parametersMap.get(MessageProcessorConstants.RETRY_INTERVAL) != null) { retryInterval = Integer.parseInt((String) parametersMap.get(MessageProcessorConstants.RETRY_INTERVAL)); } faultSeq = (String) parametersMap.get(FailoverForwardingProcessorConstants.FAULT_SEQUENCE); deactivateSeq = (String) parametersMap.get(FailoverForwardingProcessorConstants.DEACTIVATE_SEQUENCE); // Default value should be true. if (parametersMap.get(FailoverForwardingProcessorConstants.THROTTLE) != null) { isThrottling = Boolean.parseBoolean((String) parametersMap.get(FailoverForwardingProcessorConstants.THROTTLE)); } if (parametersMap.get(FailoverForwardingProcessorConstants.CRON_EXPRESSION) != null) { cronExpression = String.valueOf(parametersMap.get(FailoverForwardingProcessorConstants.CRON_EXPRESSION)); } // Default Value should be -1. if (cronExpression != null && parametersMap.get(FailoverForwardingProcessorConstants.THROTTLE_INTERVAL) != null) { throttlingInterval = Long.parseLong((String) parametersMap.get(FailoverForwardingProcessorConstants .THROTTLE_INTERVAL)); } // Default to FALSE. if (parametersMap.get(FailoverForwardingProcessorConstants.MAX_DELIVERY_DROP) != null && parametersMap.get(FailoverForwardingProcessorConstants.MAX_DELIVERY_DROP).toString() .equals("Enabled") && maxDeliverAttempts > 0) { isMaxDeliveryAttemptDropEnabled = true; } // Setting the interval value. interval = Long.parseLong((String) parametersMap.get(MessageProcessorConstants.INTERVAL)); /* * Make sure to set the isInitialized flag to TRUE in order to avoid * re-initialization. */ initialized = true; } /** * Receives the next message from the message store. * * @param msgConsumer message consumer * @return {@link MessageContext} of the last message received from the * store. */ public MessageContext fetch(MessageConsumer msgConsumer) { return messageConsumer.receive(); } /** * Sends the message to a given message store. * * @param messageContext synapse {@link MessageContext} to be sent */ public void dispatch(MessageContext messageContext) { if (log.isDebugEnabled()) { log.debug("Sending the message to client with message processor [" + messageProcessor.getName() + "]"); } SOAPEnvelope originalEnvelop = messageContext.getEnvelope(); if (targetMessageStoreName != null) { try { // Send message to the client while (!isSuccessful && !isTerminated) { try { // For each retry we need to have a fresh copy of the // actual message. otherwise retry may not // work as expected. messageContext.setEnvelope(MessageHelper.cloneSOAPEnvelope(originalEnvelop)); OMElement firstChild = null; // org.apache.axis2.context.MessageContext origAxis2Ctx = ((Axis2MessageContext) messageContext) .getAxis2MessageContext(); if (JsonUtil.hasAJsonPayload(origAxis2Ctx)) { firstChild = origAxis2Ctx.getEnvelope().getBody().getFirstElement(); } // Had to do this because // MessageHelper#cloneSOAPEnvelope does not clone // OMSourcedElemImpl correctly. if (JsonUtil.hasAJsonPayload(firstChild)) { // OMElement clonedFirstElement = messageContext.getEnvelope().getBody() .getFirstElement(); if (clonedFirstElement != null) { clonedFirstElement.detach(); messageContext.getEnvelope().getBody().addChild(firstChild); } } if (messageConsumer != null && messageConsumer.isAlive() && targetMessageStoreName != null) { targetMessageProducer = synapseEnvironment.getSynapseConfiguration().getMessageStore (targetMessageStoreName).getProducer(); if (targetMessageProducer != null) { isSuccessful = targetMessageProducer.storeMessage(messageContext); } else { isSuccessful = false; } } } catch (Exception e) { log.error("Message store messageSender of message processor [" + this.messageProcessor.getName() + "] failed to send message to the target message store"); sendThroughFaultSeq(messageContext); } if (isSuccessful) { messageConsumer.ack(); attemptCount = 0; if (log.isDebugEnabled()) { log.debug("Successfully sent the message to message store [" + targetMessageStoreName + "]" + " with message processor [" + messageProcessor.getName() + "]"); } if (messageProcessor.isPaused()) { this.messageProcessor.resumeService(); log.info("Resuming the service of message processor [" + messageProcessor.getName() + "]"); } } else { // Then we have to retry sending the message to the target // store. prepareToRetry(messageContext); } } } catch (Exception e) { log.error("Message processor [" + messageProcessor.getName() + "] failed to send the message to target store", e); } } else { /* * No Target message store defined for the Message So we do not have a * place to deliver. * Here we log a warning and remove the message * this by implementing a target inferring * mechanism. */ log.warn("Property " + FailoverForwardingProcessorConstants.TARGET_MESSAGE_STORE + " not found in the message context , Hence removing the message "); messageConsumer.ack(); } return; } /** * Sending the out message through the fault sequence. * * @param msgCtx Synapse {@link MessageContext} to be sent through the fault * sequence. */ public void sendThroughFaultSeq(MessageContext msgCtx) { if (faultSeq == null) { log.warn("Failed to send the message through the fault sequence. Sequence name does not Exist."); return; } Mediator mediator = msgCtx.getSequence(faultSeq); if (mediator == null) { log.warn("Failed to send the message through the fault sequence. Sequence [" + faultSeq + "] does not Exist."); return; } mediator.mediate(msgCtx); } /** * Sending the out message through the deactivate sequence. * * @param msgCtx Synapse {@link MessageContext} to be sent through the deactivate * sequence. */ public void sendThroughDeactivateSeq(MessageContext msgCtx) { if (deactivateSeq == null) { log.warn("Failed to send the message through the deactivate sequence. Sequence name does not Exist."); return; } Mediator mediator = msgCtx.getSequence(deactivateSeq); if (mediator == null) { log.warn("Failed to send the message through the deactivate sequence. Sequence [" + deactivateSeq + "] does not Exist."); return; } mediator.mediate(msgCtx); } /** * Terminates the job of the message processor. * * @return <code>true</code> if the job is terminated successfully, * <code>false</code> otherwise. */ public boolean terminate() { try { isTerminated = true; // Thread.currentThread().interrupt(); if (log.isDebugEnabled()) { log.debug("Successfully terminated job of message processor [" + messageProcessor.getName() + "]"); } return true; } catch (Exception e) { log.error("Failed to terminate the job of message processor [" + messageProcessor.getName() + "]"); return false; } } /* * If the max delivery attempt is reached, this will deactivate the message * processor. If the MaxDeliveryAttemptDrop is Enabled, then the message is * dropped and the message processor continues. */ private void checkAndDeactivateProcessor(MessageContext msgCtx) { if (maxDeliverAttempts > 0) { this.attemptCount++; if (attemptCount >= maxDeliverAttempts) { if (this.isMaxDeliveryAttemptDropEnabled) { dropMessageAndContinueMessageProcessor(); if (log.isDebugEnabled()) { log.debug("Message processor [" + messageProcessor.getName() + "] Dropped the failed message and continue due to reach of max attempts"); } } else { terminate(); deactivateMessageProcessor(msgCtx); if (log.isDebugEnabled()) { log.debug("Message processor [" + messageProcessor.getName() + "] stopped due to reach of max attempts"); } } } } } /* * Prepares the message processor for the next retry of delivery. */ private void prepareToRetry(MessageContext msgCtx) { if (!isTerminated) { checkAndDeactivateProcessor(msgCtx); if (log.isDebugEnabled()) { log.debug("Failed to send to target store retrying after " + retryInterval + "s with attempt count - " + attemptCount); } try { // wait for some time before retrying Thread.sleep(retryInterval); } catch (InterruptedException ignore) { // No harm even it gets interrupted. So nothing to handle. } } } private void deactivateMessageProcessor(MessageContext messageContext) { sendThroughDeactivateSeq(messageContext); this.messageProcessor.deactivate(); } private void resetService() { isSuccessful = false; attemptCount = 0; } private boolean isRunningUnderCronExpression() { return (cronExpression != null) && (throttlingInterval > -1); } private void dropMessageAndContinueMessageProcessor() { messageConsumer.ack(); attemptCount = 0; isSuccessful = true; if (this.messageProcessor.isPaused()) { this.messageProcessor.resumeService(); } log.info("Removed failed message and continue the message processor [" + this.messageProcessor.getName() + "]"); } private boolean setMessageConsumerAndProducer() { final String messageStoreName = messageProcessor.getMessageStoreName(); MessageStore messageStore = synapseEnvironment.getSynapseConfiguration().getMessageStore(messageStoreName); messageConsumer = messageStore.getConsumer(); if (messageProcessor.getParameters().get(FailoverForwardingProcessorConstants.TARGET_MESSAGE_STORE) != null) { targetMessageStoreName = (String) messageProcessor.getParameters().get (FailoverForwardingProcessorConstants.TARGET_MESSAGE_STORE); } /* * Make sure to set the same message consumer in the message processor * since it is used by life-cycle management methods. Specially by the * deactivate method to cleanup the connection before the deactivation. */ return messageProcessor.setMessageConsumer(messageConsumer); } /** * Checks whether this TaskService is properly initialized or not. * * @return <code>true</code> if this TaskService is properly initialized. * <code>false</code> otherwise. */ public boolean isInitialized() { return initialized; } public void destroy() { terminate(); } }