All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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