/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.Callable;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.synapse.SynapseException;
import org.apache.synapse.core.SynapseEnvironment;
import org.apache.synapse.message.MessageConsumer;
import org.apache.synapse.message.processor.MessageProcessorCleanupService;
import org.apache.synapse.message.processor.MessageProcessorConstants;
import org.apache.synapse.message.processor.impl.forwarder.ForwardingProcessorConstants;
import org.apache.synapse.message.senders.blocking.BlockingMsgSender;
import org.apache.synapse.task.Task;
import org.apache.synapse.task.TaskDescription;
import org.apache.synapse.task.TaskManager;
import org.apache.synapse.task.TaskManagerObserver;
import sun.misc.Service;
/**
* Implements the common message processor infrastructure which is used by the
* both <code>Forwarding</code> and <code>Sampling</code> message Processors.
* Mainly
* responsible for handling life cycle states of the message processors. Some of
* the well known life cycle states are <code>start</code>, <code>pause</code> ,
* <code>destroy</code>, <code>deactivate</code> etc.
*
*
*/
public abstract class ScheduledMessageProcessor extends AbstractMessageProcessor implements TaskManagerObserver{
private static final Log logger = LogFactory.getLog(ScheduledMessageProcessor.class.getName());
/**
* The interval at which this processor runs , default value is 1000ms
*/
protected long interval = MessageProcessorConstants.THRESHOULD_INTERVAL;
/**
* A cron expression to run the sampler
*/
protected String cronExpression = null;
/**
* This is specially used for REST scenarios where http status codes can take semantics in a RESTful architecture.
*/
protected String[] nonRetryStatusCodes = null;
protected BlockingMsgSender sender;
protected SynapseEnvironment synapseEnvironment;
private TaskManager taskManager = null;
private int memberCount = 1;
private static final String TASK_PREFIX = "MSMP_";
private static final String DEFAULT_TASK_SUFFIX = "0";
@Override
public void init(SynapseEnvironment se) {
this.synapseEnvironment = se;
initMessageSender(parameters);
super.init(se);
/*
* initialize the task manager only once to alleviate complexities
* related to the pending tasks.
*/
if (taskManager == null) {
taskManager = synapseEnvironment.getSynapseConfiguration().getTaskManager();
}
if (taskManager == null) {
throw new SynapseException("Task Manager not defined in the configuration.");
}
/*
* If the task manager is not initialized yet, subscribe to
* initialization completion event here.
*/
if (!taskManager.isInitialized()) {
taskManager.addObserver(this);
return;
}
/*
* Schedule the task despite if it is ACTIVATED OR DEACTIVATED
* initially. Eventhough the Message Processor is explicitly deactivated
* initially, we need to have a Task to handle subsequent updates.
*/
this.start();
}
/*
* Fetches the value of the IsActivated Property of Message Processor. The
* IsActivated property resides under the Advanced parameters section of the
* Message Processor.
*/
public boolean getIsActivatedParamValue() {
Object isActiveParam = parameters.get(MessageProcessorConstants.IS_ACTIVATED);
// Message Processor is ACTIVATED by default.
boolean isActivated = true;
if (isActiveParam != null) {
isActivated = Boolean.parseBoolean(String.valueOf(isActiveParam));
}
return isActivated;
}
protected boolean isProcessorStartAsDeactivated(){
return !getIsActivatedParamValue();
}
@Override
public boolean start() {
for (int i = 0; i < memberCount; i++) {
/*
* Make sure to fetch the task after initializing the message sender
* and consumer properly. Otherwise you may get NullPointer
* exceptions.
*/
Task task = this.getTask();
TaskDescription taskDescription = new TaskDescription();
taskDescription.setName(TASK_PREFIX + name + i);
taskDescription.setTaskGroup(MessageProcessorConstants.SCHEDULED_MESSAGE_PROCESSOR_GROUP);
/*
* If this interval value is less than 1000 ms, ntask will throw an
* exception while building the task. So to get around that we are
* setting threshold interval value of 1000 ms to the task
* description here. But actual interval value may be less than 1000
* ms, and hence isThrotling is set to TRUE.
*/
if (interval < MessageProcessorConstants.THRESHOULD_INTERVAL) {
taskDescription.setInterval(MessageProcessorConstants.THRESHOULD_INTERVAL);
} else {
taskDescription.setInterval(interval);
}
taskDescription.setIntervalInMs(true);
taskDescription.addResource(TaskDescription.INSTANCE, task);
taskDescription.addResource(TaskDescription.CLASSNAME, task.getClass().getName());
/*
* If there is a Cron Expression we need to set it into the
* TaskDescription so that the framework will take care of it.
*/
if (cronExpression != null) {
taskDescription.setCronExpression(cronExpression);
}
taskManager.schedule(taskDescription);
}
logger.info("Started message processor. [" + getName() + "].");
/*
* If the Message Processor is Deactivated through the Advanced parameter
* explicitly, then we deactivate the task immediately.
*/
if (!getIsActivatedParamValue()) {
deactivate();
}
return true;
}
@Override
public boolean isDeactivated() {
return taskManager.isTaskDeactivated(TASK_PREFIX + name +
DEFAULT_TASK_SUFFIX);
}
@Override
public void setParameters(Map<String, Object> parameters) {
super.setParameters(parameters);
if (parameters != null && !parameters.isEmpty()) {
Object o = parameters.get(MessageProcessorConstants.CRON_EXPRESSION);
if (o != null) {
cronExpression = o.toString();
}
o = parameters.get(MessageProcessorConstants.INTERVAL);
if (o != null) {
interval = Integer.parseInt(o.toString());
}
o = parameters.get(MessageProcessorConstants.MEMBER_COUNT);
if (o != null) {
memberCount = Integer.parseInt(o.toString());
}
o = parameters.get(MessageProcessorConstants.IS_ACTIVATED);
if (o != null) {
setActivated(Boolean.valueOf(o.toString()));
}
o = parameters.get(ForwardingProcessorConstants.NON_RETRY_STATUS_CODES);
if (o != null) {
// we take it out of param set and send it because we need split
// the array.
nonRetryStatusCodes = o.toString().split(",");
}
}
}
@Override
public boolean stop() {
/*
* There could be servers that are disabled at startup time.
* therefore not started but initiated.
*/
if (taskManager != null && taskManager.isInitialized()) {
for (int i = 0; i < memberCount; i++) {
/*
* This is to immediately stop the scheduler to avoid firing new
* services
*/
if (taskManager.isTaskExist(TASK_PREFIX + name + i) &&
taskManager.isTaskRunning(TASK_PREFIX + name + i)) {
taskManager.pause(TASK_PREFIX + name + i);
}
if (logger.isDebugEnabled()) {
logger.debug("ShuttingDown Message Processor Scheduler : " +
taskManager.getName());
}
/*
* This value should be given in the format -->
* taskname::taskgroup.
* Otherwise a default group is assigned by the ntask task
* manager.
*/
taskManager.delete(TASK_PREFIX + name + i + "::" +
MessageProcessorConstants.SCHEDULED_MESSAGE_PROCESSOR_GROUP);
//even the task is existed or not at Task REPO we need to clear the NTaskAdaptor
//synapseTaskProperties map which holds taskName and TASK Instance for expired TASK at undeployment.
//taskManager.delete() method does that. There is a possibility to reinitialize those expired tasks
//from Ntask core if the expired task existed in mentioned property map in worker nodes.
//Even the manager first deletes the Task from Ntask core task repository, at that
//point all the workers see this task as non existing task but the property map will not be updated
//as we are not triggering taskManager.delete() to non existing task. Which in that case all worker node's
//property map leaves expired tasks which ultimate lead to re initialize them.
}
if (logger.isDebugEnabled()) {
logger.debug("Stopped message processor [" + getName() + "].");
}
return true;
}
return false;
}
@Override
public void destroy() {
try {
stop();
}
finally {
/*
* If the Task is scheduled with an interval value < 1000 ms, it is
* executed outside Quartz. For an example Task with interval 200ms.
* Here we directly pause the task, move on and cleanup the JMS
* connection.But actual pausing the task through TaskManager takes
* few ms (say 300 ms). During this time the Task is executed
* outside Quartz and
* a new JMS connection is created (After the previous cleanup).
* Once
* the task is paused and destroyed successfully, we create a new
* task, for which a new JMS
* connection is created. This leads to multiple JMS connections and
* is a Bug. This 1000 ms sleep is used to make sure that the task
* is paused before cleaning up the JMS connection, which prevents
* multiple JMS connections being created.
*/
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
logger.error("The thread was interrupted while sleeping");
}
if (getMessageConsumer() != null && messageConsumers.size() > 0) {
cleanupLocalResources();
} else {
logger.warn("[" + getName() + "] Could not find the message consumer to cleanup.");
}
/*
* Cleaning up the resources in the cluster mode here.
*/
taskManager.sendClusterMessage(getMessageProcessorCleanupTask());
}
if (logger.isDebugEnabled()) {
logger.info("Successfully destroyed message processor [" + getName() + "].");
}
}
@Override
public boolean deactivate() {
if (taskManager != null && taskManager.isInitialized()) {
try {
if (logger.isDebugEnabled()) {
logger.debug("Deactivating message processor [" + getName() + "]");
}
pauseService();
logger.info("Successfully deactivated the message processor [" + getName() + "]");
} finally {
/*
* This will close the connection with the JMS Provider/message
* store.
*/
cleanupLocalResources();
/*
* Cleaning up the resources in the cluster mode here.
*/
taskManager.sendClusterMessage(getMessageProcessorCleanupTask());
}
return true;
} else {
return false;
}
}
@Override
public boolean activate() {
/*
* Checking whether it is already deactivated. If it is deactivated only
* we can activate again.
*/
if (taskManager != null && isDeactivated()) {
if (logger.isDebugEnabled()) {
logger.debug("Starting Message Processor Scheduler : " + taskManager.getName());
}
resumeService();
logger.info("Successfully re-activated the message processor [" + getName() + "]");
return true;
} else {
return false;
}
}
@Override
public void pauseService() {
for (int i = 0; i < memberCount; i++) {
taskManager.pause(TASK_PREFIX + name + i);
}
}
@Override
public void resumeService() {
for (int i = 0; i < memberCount; i++) {
taskManager.resume(TASK_PREFIX + name + i);
}
}
public boolean isActive() {
/*
* Sometimes when the backend is down, we may retry the delivery for a
* specified number of attempts. Due to that control is not returned
* back to the Quartz for some time (may be few secs depending on the
* config). Therefore the task is in Blocked
* state as far as Quartz is concerned. But we are running the execute
* method of the task in the meantime with our own logic. Therefore
* though the task is blocked from Quartz, still we are executing it. So
* that implies it is running.
*/
return taskManager.isTaskRunning(TASK_PREFIX + name + DEFAULT_TASK_SUFFIX) ||
taskManager.isTaskBlocked(TASK_PREFIX + name + DEFAULT_TASK_SUFFIX);
}
@Override
public boolean isPaused() {
return taskManager.isTaskDeactivated(TASK_PREFIX + name + DEFAULT_TASK_SUFFIX);
}
public boolean getActivated() {
return taskManager.isTaskRunning(TASK_PREFIX + name + DEFAULT_TASK_SUFFIX);
}
private void setActivated(boolean activated) {
parameters.put(MessageProcessorConstants.IS_ACTIVATED, String.valueOf(activated));
}
/**
* nTask does not except values less than 1000 for its schedule interval.
* Therefore when the
* interval is less than 1000 ms we have
* to handle it as a separate case.
*
* @param interval
* in which scheduler triggers its job.
* @return true if it needs to run on throttle mode, <code>false</code>
* otherwise.
*/
protected boolean isThrottling(final long interval) {
return interval < MessageProcessorConstants.THRESHOULD_INTERVAL;
}
public boolean isThrottling(final String cronExpression) {
return cronExpression != null;
}
private BlockingMsgSender initMessageSender(Map<String, Object> params) {
String axis2repo = (String) params.get(ForwardingProcessorConstants.AXIS2_REPO);
String axis2Config = (String) params.get(ForwardingProcessorConstants.AXIS2_CONFIG);
sender = new BlockingMsgSender();
if (axis2repo != null) {
sender.setClientRepository(axis2repo);
}
if (axis2Config != null) {
sender.setAxis2xml(axis2Config);
}
sender.init();
return sender;
}
/**
* Gives the {@link Task} instance associated with this processor.
*
* @return {@link Task} associated with this processor.
*/
protected abstract Task getTask();
@Override
public void update() {
start();
}
@Override
public void cleanupLocalResources() {
if (messageConsumers != null) {
for (MessageConsumer messageConsumer : messageConsumers) {
messageConsumer.cleanup();
}
}
}
private Callable<Void> getMessageProcessorCleanupTask() {
MessageProcessorCleanupService cleanupTask = null;
if (logger.isDebugEnabled()) {
logger.debug("Trying to fetch InboundRequestProcessor from classpath.. ");
}
Iterator<MessageProcessorCleanupService> it =
Service.providers(MessageProcessorCleanupService.class);
while (it.hasNext()) {
cleanupTask = it.next();
cleanupTask.setName(name);
if (cleanupTask != null) {
if (logger.isDebugEnabled()) {
logger.debug("Message Processor Cleanup Service found : " +
cleanupTask.getClass().getName());
}
}
return cleanupTask;
}
return null;
}
}