/**
* 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.emitter;
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.strategy.StreamingMessageQueueWaitStrategy;
/**
* Provides a runtime environment for {@link Emitter} instances. The environment retrieves all
* incoming {@link StreamingDataMessage} instances from the attached {@link StreamingMessageQueueConsumer}
* and provides them to the assigned {@link Emitter} for further processing.
* @author mnxfst
*
*/
public class EmitterRuntimeEnvironment implements Runnable {
/** our faithful logging facility ... ;-) */
private static final Logger logger = Logger.getLogger(EmitterRuntimeEnvironment.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 emitter assigned to this runtime environment */
private final String emitterId;
/** reference to emitter instance being fed with incoming messages */
private final Emitter emitter;
/** provides read access to assigned source queue */
private final StreamingMessageQueueConsumer queueConsumer;
/** indicates whether the environment is still running */
private boolean running = false;
/** message counter metric */
private Counter messageCounter = null;
/** insertion timer metric */
private Timer messageEmitDurationTimer = null;
/**
* Initializes the runtime environment using the provided input
* @param processingNodeId
* @param pipelineId
* @param emitter
* @param queueConsumer
* @throws RequiredInputMissingException
*/
public EmitterRuntimeEnvironment(final String processingNodeId, final String pipelineId, final Emitter emitter,
final StreamingMessageQueueConsumer queueConsumer) throws RequiredInputMissingException {
///////////////////////////////////////////////////////////////////
// validate input
if(StringUtils.isBlank(processingNodeId))
throw new RequiredInputMissingException("Missing required processing node identifier");
if(StringUtils.isBlank(pipelineId))
throw new RequiredInputMissingException("Missing required pipeline identifier");
if(emitter == null)
throw new RequiredInputMissingException("Missing required emitter");
if(queueConsumer == null)
throw new RequiredInputMissingException("Missing required input queue consumer");
//
///////////////////////////////////////////////////////////////////
this.processingNodeId = StringUtils.lowerCase(StringUtils.trim(processingNodeId));
this.pipelineId = StringUtils.lowerCase(StringUtils.trim(pipelineId));
this.emitterId = StringUtils.lowerCase(StringUtils.trim(emitter.getId()));
this.emitter = emitter;
this.queueConsumer = queueConsumer;
this.running = true;
if(logger.isDebugEnabled())
logger.debug("emitter init [node="+this.processingNodeId+", pipeline="+this.pipelineId+", emitter="+this.emitterId+"]");
}
/**
* @see java.lang.Runnable#run()
*/
public void run() {
// fetch the wait strategy attached to the queue (provided through the queue consumer)
StreamingMessageQueueWaitStrategy queueWaitStrategy = this.queueConsumer.getWaitStrategy();
while(running) {
try {
// fetch message from queue consumer via strategy
StreamingDataMessage message = queueWaitStrategy.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.messageEmitDurationTimer != null ? this.messageEmitDurationTimer.time() : null);
this.emitter.onMessage(message);
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+", emitter="+this.emitterId+"]: " + e.getMessage(), e);
// TODO add handler for responding to errors
}
}
}
/**
* Shuts down the runtime environment as well as the attached {@link Emitter}
*/
public void shutdown() {
this.running = false;
try {
this.emitter.shutdown();
if(logger.isDebugEnabled())
logger.debug("emitter shutdown [node="+this.processingNodeId+", pipeline="+this.pipelineId+", emitter="+this.emitterId+"]");
} catch(Exception e) {
logger.error("emitter shutdown error [node="+this.processingNodeId+", pipeline="+this.pipelineId+", emitter="+this.emitterId+"]: " + e.getMessage(), e);
}
}
/**
* @return the running
*/
public boolean isRunning() {
return running;
}
/**
* @param messageCounter the messageCounter to set
*/
public void setMessageCounter(Counter messageCounter) {
this.messageCounter = messageCounter;
}
/**
* @param messageEmitDurationTimer the messageEmitDurationTimer to set
*/
public void setMessageEmitDurationTimer(Timer messageEmitDurationTimer) {
this.messageEmitDurationTimer = messageEmitDurationTimer;
}
}