
org.kaleidofoundry.messaging.AbstractConsumer Maven / Gradle / Ivy
/*
* Copyright 2008-2021 the original author or authors
*
* 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.kaleidofoundry.messaging;
import static org.kaleidofoundry.messaging.ClientContextBuilder.CONSUMER_PRINT_PROCESSED_MESSAGES_MODULO;
import static org.kaleidofoundry.messaging.ClientContextBuilder.CONSUMER_RESPONSE_DURATION;
import static org.kaleidofoundry.messaging.ClientContextBuilder.CONSUMER_THREAD_POOL_WAIT_ON_SHUTDOWN;
import static org.kaleidofoundry.messaging.ClientContextBuilder.CONSUMER_THREAD_PREFIX_PROPERTY;
import static org.kaleidofoundry.messaging.ClientContextBuilder.DEBUG_PROPERTY;
import static org.kaleidofoundry.messaging.ClientContextBuilder.THREAD_POOL_COUNT_PROPERTY;
import static org.kaleidofoundry.messaging.ClientContextBuilder.TRANSPORT_REF;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.kaleidofoundry.core.context.EmptyContextParameterException;
import org.kaleidofoundry.core.context.RuntimeContext;
import org.kaleidofoundry.core.lang.annotation.Task;
import org.kaleidofoundry.core.util.StringHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author jraduget
*/
@Task(comment = "I18n messages")
public abstract class AbstractConsumer implements Consumer {
/** Default consumers logger */
protected static final Logger LOGGER = LoggerFactory.getLogger(Consumer.class);
/** Consumers logger use for statistics */
protected final Logger STATISTICS_LOGGER;
/** Logger line separator */
static final String DEBUG_SEPARATOR = StringHelper.replicate("-", 120);
// consumer message counters
private final AtomicInteger ProcessedMessagesOK = new AtomicInteger(0);
private final AtomicInteger ProcessedMessagesKO = new AtomicInteger(0);
private final AtomicInteger ProcessedMessagesSKIPPED = new AtomicInteger(0);
private final AtomicLong AverageResponseTime = new AtomicLong(0);
/**
* Message wrapper used by consumer processing
*/
public class MessageWrapper {
private Message message;
private Object providerObject;
private Throwable error;
private boolean ackMessage = true;
public Message getMessage() {
return message;
}
public void setMessage(Message message) {
this.message = message;
}
public Throwable getError() {
return error;
}
public void setError(Throwable error) {
this.error = error;
}
public boolean isAckMessage() {
return ackMessage;
}
public void setAckMessage(boolean ackMessage) {
this.ackMessage = ackMessage;
}
public Object getProviderObject() {
return providerObject;
}
public void setProviderObject(Object providerObject) {
this.providerObject = providerObject;
}
public boolean hasError() {
return error != null;
}
}
/**
* Consumer worker
*/
public abstract class ConsumerWorker extends Thread {
public ConsumerWorker(int index, String name) {
super(name);
}
/**
* worker initialize, execute once the first time
*
* @throws TransportException
*/
public abstract void init() throws TransportException;
/**
* The message receiver method, to be defined by the provider.
* No exception must be thrown by this method, use messageWrapper.setError
*
* @param messageWrapper wrapper that contains message and error
*/
public abstract void receive(MessageWrapper messageWrapper);
/**
* message acknowledgment, it will be executed after {@link #receive(MessageWrapper)}, only if all registered {@link MessageHandler}
* do
* not throws error
*
* @param messageWrapper
*/
public abstract void acknowledge(MessageWrapper messageWrapper) throws MessagingException;
/*
* (non-Javadoc)
* @see java.lang.Thread#run()
*/
@Override
public void run() {
initRun();
while (!pool.isShutdown()) {
long beginTimeStamp = 0;
boolean noError = true;
MessageWrapper messageWrapper = new MessageWrapper();
try {
// wait and receive the new incoming message
receive(messageWrapper);
// start processing
beginTimeStamp = System.currentTimeMillis();
// after receiving
noError = onceReceived(messageWrapper);
} catch (Throwable th) {
// all must have been done in the receive or onceReceived methods
noError = false;
} finally {
// handle the minimum response time of a message (if specified)
final long responseTime = System.currentTimeMillis() - beginTimeStamp;
final long waitingDuration = context.getLong(CONSUMER_RESPONSE_DURATION, 0l) - responseTime;
if (!noError && waitingDuration > 0) {
try {
sleep(waitingDuration);
} catch (InterruptedException ite) {
}
}
// print statistics about processed messages
int printProcessedMessagesModulo = context.getInteger(CONSUMER_PRINT_PROCESSED_MESSAGES_MODULO, -1);
if (printProcessedMessagesModulo > 0) {
int processedMessagesOK = ProcessedMessagesOK.get();
int processedMessagesKO = ProcessedMessagesKO.get();
int processedMessagesSkipped = ProcessedMessagesSKIPPED.get();
if ((processedMessagesOK + processedMessagesKO + processedMessagesSkipped) % printProcessedMessagesModulo == 0) {
long averageResponseTime = (AverageResponseTime.get() + responseTime) / 2;
if ((processedMessagesOK + processedMessagesKO + processedMessagesSkipped) % printProcessedMessagesModulo == 0) {
STATISTICS_LOGGER.info("consumer statistics : name={} ; msg OK={} ; msg KO={} ; msg SKIPPED={} ; msg response time={}ms", new Object[] {
getName(), processedMessagesOK, processedMessagesKO, processedMessagesSkipped, averageResponseTime });
}
}
}
}
}
}
/*
* (non-Javadoc)
* @see java.lang.Thread#destroy()
*/
@Override
public void destroy() {
LOGGER.info("{} destroyed", getName());
}
protected boolean isDebug() {
return context.getBoolean(DEBUG_PROPERTY, false) || LOGGER.isDebugEnabled();
}
protected final void initRun() {
try {
init();
} catch (TransportException e) {
throw new IllegalStateException("transport initialization error", e);
} finally {
LOGGER.info("{} listening", getName());
}
}
/**
* @param messageWrapper
* @return true
of no errors, false
otherwise
* @throws MessagingException
*/
protected final boolean onceReceived(MessageWrapper messageWrapper) throws MessagingException {
boolean noError = true;
MessageHandler handlerInError = null;
// the message wrapper has no error and a message have been received
if (!messageWrapper.hasError() && messageWrapper.getMessage() != null) {
if (isDebug()) {
LOGGER.info(
"<<< receiving message with providerId={} , correlationId={} , parameters={}",
new Object[] { messageWrapper.getMessage().getProviderId(), messageWrapper.getMessage().getCorrelationId(),
String.valueOf(messageWrapper.getMessage().getParameters()) });
LOGGER.info("{}", messageWrapper.getMessage().toString());
}
// Processing handler
boolean skipMessage = false;
for (MessageHandler handler : messageHandlers) {
try {
if (!handler.onReceive(messageWrapper.getMessage())) {
skipMessage = true;
break;
}
} catch (Throwable th) {
handlerInError = handler;
messageWrapper.setError(th);
}
}
if (!skipMessage) {
ProcessedMessagesOK.incrementAndGet();
acknowledge(messageWrapper);
} else {
ProcessedMessagesSKIPPED.incrementAndGet();
}
}
// check again if the message wrapper has error, if can occurred during the handler chain processing
if (messageWrapper.hasError()) {
if (messageWrapper.getError() instanceof MessageTimeoutException) {
// nothing to do, but skip the loop
noError = false;
LOGGER.info(messageWrapper.getError().getMessage());
} else {
// other exception to handle
noError = false;
ProcessedMessagesKO.incrementAndGet();
if (handlerInError != null) {
LOGGER.error("an error occurred on the chain handler processing of this consumer", messageWrapper.getError());
handlerInError.onError(messageWrapper.getMessage(), messageWrapper.getError());
} else {
LOGGER.error("an error occurred on this consumer", messageWrapper.getError());
}
}
}
return noError;
}
protected final void printResponseTime(long beginTimeStamp) {
final long responseTime = System.currentTimeMillis() - beginTimeStamp;
LOGGER.info("processing message in {} ms", String.valueOf(responseTime));
}
}
protected final RuntimeContext context;
protected final Transport transport;
@Task(comment = "Lazy creation of the executor service for google application engine, use com.google.appengine.api.taskqueue.Queue ?")
protected ExecutorService pool;
private final List messageHandlers;
public AbstractConsumer(RuntimeContext context) {
this.context = context;
this.messageHandlers = Collections.synchronizedList(new LinkedList());
// transport
String transportRef = this.context.getString(TRANSPORT_REF);
if (!StringHelper.isEmpty(transportRef)) {
this.transport = TransportFactory.provides(transportRef, context);
this.transport.getConsumers().put(getName(), this);
} else {
throw new EmptyContextParameterException(TRANSPORT_REF, context);
}
// logger use for statistics
STATISTICS_LOGGER = LoggerFactory.getLogger(Consumer.class.getName() + "." + getName());
}
/*
* (non-Javadoc)
* @see org.kaleidofoundry.messaging.Consumer#getName()
*/
@Override
public String getName() {
return context.getName();
}
/*
* (non-Javadoc)
* @see org.kaleidofoundry.messaging.Client#getTransport()
*/
@Override
public Transport getTransport() throws TransportException {
return transport;
}
/*
* (non-Javadoc)
* @see org.kaleidofoundry.messaging.Consumer#start()
*/
@Override
public synchronized void start() throws TransportException {
ProcessedMessagesOK.set(0);
ProcessedMessagesKO.set(0);
ProcessedMessagesSKIPPED.set(0);
// shutdown the pool if needed
stop();
// thread executor service
int threadCount = this.context.getInteger(THREAD_POOL_COUNT_PROPERTY, 1);
LOGGER.info("Creating consumer [{}] with a thread pool size of {}", context.getName(), threadCount);
this.pool = Executors.newFixedThreadPool(threadCount);
// submit thread to the pool
String threadPrefix = this.context.getString(CONSUMER_THREAD_PREFIX_PROPERTY, context.getName());
for (int cpt = 0; cpt < threadCount; cpt++) {
this.pool.submit(newWorker(threadPrefix + "[" + StringHelper.leftPad(String.valueOf(cpt + 1), 3, '0') + "]", cpt + 1));
}
}
/*
* (non-Javadoc)
* @see org.kaleidofoundry.messaging.Consumer#stop()
*/
@Override
public synchronized void stop() throws TransportException {
if (pool != null) {
int shutdownWaitInSeconds = this.context.getInteger(CONSUMER_THREAD_POOL_WAIT_ON_SHUTDOWN, 5);
LOGGER.info("Shutdown consumer [{}] thread pool", context.getName());
pool.shutdown();
try {
if (!pool.awaitTermination(shutdownWaitInSeconds, TimeUnit.SECONDS)) {
pool.shutdownNow();
if (!pool.awaitTermination(shutdownWaitInSeconds, TimeUnit.SECONDS)) {
LOGGER.error("Error trying shutdown consumer [{}] thread pool", context.getName());
}
}
} catch (InterruptedException ie) {
pool.shutdownNow();
Thread.currentThread().interrupt();
}
}
this.transport.getConsumers().remove(getName());
}
/*
* (non-Javadoc)
* @see org.kaleidofoundry.messaging.Consumer#addMessageHandler(org.kaleidofoundry.messaging.MessageHandler)
*/
@Override
public Consumer addMessageHandler(MessageHandler handler) {
if (!messageHandlers.contains(handler)) {
messageHandlers.add(handler);
}
return this;
}
/**
* @param workerName
* @param workerIndex
* @throws TransportException
*/
protected abstract ConsumerWorker newWorker(String workerName, int workerIndex) throws TransportException;
/*
* (non-Javadoc)
* @see org.kaleidofoundry.messaging.Client#getStatistics()
*/
@Override
public UsageStatistics getStatistics() {
return new UsageStatistics(ProcessedMessagesOK.get(), ProcessedMessagesKO.get(), ProcessedMessagesSKIPPED.get());
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy