/** * 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.camel.component.rabbitmq; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import com.rabbitmq.client.Connection; import org.apache.camel.Processor; import org.apache.camel.Suspendable; import org.apache.camel.impl.DefaultConsumer; public class RabbitMQConsumer extends DefaultConsumer implements Suspendable { private ExecutorService executor; private Connection conn; private int closeTimeout = 30 * 1000; private final RabbitMQEndpoint endpoint; /** * Task in charge of starting consumer */ private StartConsumerCallable startConsumerCallable; /** * Running consumers */ private final List<RabbitConsumer> consumers = new ArrayList<RabbitConsumer>(); public RabbitMQConsumer(RabbitMQEndpoint endpoint, Processor processor) { super(endpoint, processor); this.endpoint = endpoint; } @Override public RabbitMQEndpoint getEndpoint() { return (RabbitMQEndpoint)super.getEndpoint(); } /** * Open connection */ private void openConnection() throws IOException, TimeoutException { log.trace("Creating connection..."); this.conn = getEndpoint().connect(executor); log.debug("Created connection: {}", conn); } /** * Returns the exiting open connection or opens a new one * @throws IOException * @throws TimeoutException */ protected synchronized Connection getConnection() throws IOException, TimeoutException { if (this.conn != null && this.conn.isOpen()) { return this.conn; } log.debug("The existing connection is closed"); openConnection(); return this.conn; } /** * Add a consumer thread for given channel */ private void startConsumers() throws IOException { // Create consumers but don't start yet for (int i = 0; i < endpoint.getConcurrentConsumers(); i++) { createConsumer(); } // Try starting consumers (which will fail if RabbitMQ can't connect) try { for (RabbitConsumer consumer : this.consumers) { consumer.start(); } } catch (Exception e) { log.info("Connection failed, will start background thread to retry!", e); reconnect(); } } /** * Add a consumer thread for given channel */ private void createConsumer() throws IOException { RabbitConsumer consumer = new RabbitConsumer(this); this.consumers.add(consumer); } private synchronized void reconnect() { if (startConsumerCallable != null) { return; } // Open connection, and start message listener in background Integer networkRecoveryInterval = getEndpoint().getNetworkRecoveryInterval(); final long connectionRetryInterval = networkRecoveryInterval != null && networkRecoveryInterval > 0 ? networkRecoveryInterval : 100L; startConsumerCallable = new StartConsumerCallable(connectionRetryInterval); executor.submit(startConsumerCallable); } /** * If needed, close Connection and Channels */ private void closeConnectionAndChannel() throws IOException, TimeoutException { if (startConsumerCallable != null) { startConsumerCallable.stop(); } for (RabbitConsumer consumer : this.consumers) { try { consumer.stop(); } catch (TimeoutException e) { log.warn("Timeout occurred while stopping consumer. This exception is ignored", e); } } this.consumers.clear(); if (conn != null) { log.debug("Closing connection: {} with timeout: {} ms.", conn, closeTimeout); conn.close(closeTimeout); conn = null; } } @Override protected void doSuspend() throws Exception { closeConnectionAndChannel(); } @Override protected void doResume() throws Exception { reconnect(); } @Override protected void doStart() throws Exception { executor = endpoint.createExecutor(); log.debug("Using executor {}", executor); startConsumers(); } @Override protected void doStop() throws Exception { closeConnectionAndChannel(); if (executor != null) { if (endpoint != null && endpoint.getCamelContext() != null) { endpoint.getCamelContext().getExecutorServiceManager().shutdownNow(executor); } else { executor.shutdownNow(); } executor = null; } } /** * Task in charge of opening connection and adding listener when consumer is * started and broker is not available. */ private class StartConsumerCallable implements Callable<Void> { private final long connectionRetryInterval; private final AtomicBoolean running = new AtomicBoolean(true); StartConsumerCallable(long connectionRetryInterval) { this.connectionRetryInterval = connectionRetryInterval; } public void stop() { running.set(false); RabbitMQConsumer.this.startConsumerCallable = null; } @Override public Void call() throws Exception { boolean connectionFailed = true; // Reconnection loop while (running.get() && connectionFailed) { try { for (RabbitConsumer consumer : consumers) { consumer.reconnect(); } connectionFailed = false; } catch (Exception e) { log.info("Connection failed, will retry in " + connectionRetryInterval + "ms", e); Thread.sleep(connectionRetryInterval); } } if (!connectionFailed) { startConsumers(); } stop(); return null; } } }