/**
* Copyright 2015 Otto (GmbH & Co KG)
*
* 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 com.ottogroup.bi.spqr.pipeline.component.operator;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Timer;
import com.ottogroup.bi.spqr.exception.RequiredInputMissingException;
import com.ottogroup.bi.spqr.pipeline.message.StreamingDataMessage;
import com.ottogroup.bi.spqr.pipeline.queue.StreamingMessageQueueConsumer;
import com.ottogroup.bi.spqr.pipeline.queue.StreamingMessageQueueProducer;
import com.ottogroup.bi.spqr.pipeline.queue.strategy.StreamingMessageQueueWaitStrategy;
/**
* Provides a runtime environment for {@link DirectResponseOperator} instances. The environment polls
* messages from the assigned {@link StreamingMessageQueueConsumer}, forwards them for further processing
* to the {@link DirectResponseOperator} and inserts all generated {@link StreamingDataMessage response messages}
* into the {@link StreamingMessageQueueProducer}. The message order as received from the operator is
* preserved when handing over the messages to the queue producer.
* @author mnxfst
* @since Mar 5, 2015
*/
public class DirectResponseOperatorRuntimeEnvironment implements Runnable {
/** our faithful logging facility ... ;-) */
private static final Logger logger = Logger.getLogger(DirectResponseOperatorRuntimeEnvironment.class);
/** identifier of processing node the runtime environment belongs to*/
private final String processingNodeId;
/** identifier of pipeline the runtime environment belongs to */
private final String pipelineId;
/** identifier of operator assigned to this runtime environment */
private final String operatorId;
/** operator instance executed by this runtime environment */
private final DirectResponseOperator directResponseOperator;
/** provides read access to assigned source queue */
private final StreamingMessageQueueConsumer queueConsumer;
/** provides write access to assigned destination queue */
private final StreamingMessageQueueProducer queueProducer;
/** indicates whether the operator runtime is still running or not */
private boolean running = false;
/** consumer queue wait strategy */
private final StreamingMessageQueueWaitStrategy consumerQueueWaitStrategy;
/** destination queue wait strategy */
private final StreamingMessageQueueWaitStrategy destinationQueueWaitStrategy;
/** message counter metric */
private Counter messageCounter = null;
/** message processing timer metric */
private Timer messageProcessingTimer = null;
/**
* Initializes the operator runtime environment using the provided input
* @param processingNodeId
* @param pipelineId
* @param directResponseOperator
* @param queueConsumer
* @param queueProducer
*/
public DirectResponseOperatorRuntimeEnvironment(final String processingNodeId, final String pipelineId, final DirectResponseOperator directResponseOperator, final StreamingMessageQueueConsumer queueConsumer,
final StreamingMessageQueueProducer queueProducer) throws RequiredInputMissingException {
/////////////////////////////////////////////////////////////
// input validation
if(StringUtils.isBlank(processingNodeId))
throw new RequiredInputMissingException("Missing required processing node identifier");
if(StringUtils.isBlank(pipelineId))
throw new RequiredInputMissingException("Missing required pipeline identifier");
if(directResponseOperator == null)
throw new RequiredInputMissingException("Missing required direct response operator");
if(queueConsumer == null)
throw new RequiredInputMissingException("Missing required queue consumer");
if(queueProducer == null)
throw new RequiredInputMissingException("Missing required queue producer");
//
/////////////////////////////////////////////////////////////
this.processingNodeId = StringUtils.lowerCase(StringUtils.trim(processingNodeId));
this.pipelineId = StringUtils.lowerCase(StringUtils.trim(pipelineId));
this.operatorId = StringUtils.lowerCase(StringUtils.trim(directResponseOperator.getId()));
this.directResponseOperator = directResponseOperator;
this.queueConsumer = queueConsumer;
this.queueProducer = queueProducer;
this.running = true;
this.consumerQueueWaitStrategy = queueConsumer.getWaitStrategy();
this.destinationQueueWaitStrategy = queueProducer.getWaitStrategy();
if(logger.isDebugEnabled())
logger.debug("direct response operator init [node="+this.processingNodeId+", pipeline="+this.pipelineId+", operator="+this.operatorId+"]");
}
/**
* @see java.lang.Runnable#run()
*/
public void run() {
while(running) {
try {
StreamingDataMessage message = this.consumerQueueWaitStrategy.waitFor(this.queueConsumer);
if(message != null && message.getBody() != null) {
@SuppressWarnings("resource") // context#close() calls context#stop -> avoid additional call, thus accept warning
Timer.Context timerContext = (this.messageProcessingTimer != null ? this.messageProcessingTimer.time() : null);
StreamingDataMessage[] responseMessages = this.directResponseOperator.onMessage(message);
if(responseMessages != null && responseMessages.length > 0) {
for(final StreamingDataMessage responseMessage : responseMessages) {
this.queueProducer.insert(responseMessage);
}
this.destinationQueueWaitStrategy.forceLockRelease();
}
if(timerContext != null)
timerContext.stop();
if(this.messageCounter != null)
this.messageCounter.inc();
}
} catch(InterruptedException e) {
// do nothing - waiting was interrupted
} catch(Exception e) {
logger.error("processing error [node="+this.processingNodeId+", pipeline="+this.pipelineId+", operator="+this.operatorId+"]: " + e.getMessage(), e);
// TODO add handler for responding to errors
}
}
}
/**
* Shuts down the runtime environment as well as the attached {@link Operator}
*/
public void shutdown() {
this.running = false;
try {
this.directResponseOperator.shutdown();
} catch(Exception e) {
logger.error("operator shutdown error [node="+this.processingNodeId+", pipeline="+this.pipelineId+", operator="+this.operatorId+"]: " + e.getMessage(), e);
}
if(logger.isDebugEnabled())
logger.debug("shutdown success [node="+this.processingNodeId+", pipeline="+this.pipelineId+", operator="+this.operatorId+"]");
}
/**
* @return the running
*/
public boolean isRunning() {
return running;
}
/**
* @param messageCounter the messageCounter to set
*/
public void setMessageCounter(Counter messageCounter) {
this.messageCounter = messageCounter;
}
/**
* @param messageProcessingTimer the messageProcessingTimer to set
*/
public void setMessageProcessingTimer(Timer messageProcessingTimer) {
this.messageProcessingTimer = messageProcessingTimer;
}
}