Java tutorial
/* * Copyright 2013 David Tinker * * 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 io.qdb.server.input; import com.rabbitmq.client.*; import io.qdb.server.ExpectedIOException; import io.qdb.server.model.Input; import io.qdb.server.model.Queue; import java.io.IOException; import java.net.ConnectException; /** * Fetches messages from a RabbitMQ queue. */ public class RabbitMQInputHandler extends InputHandlerAdapter implements ShutdownListener { public String exchange; public String queue; public String routingKey = ""; public int heartbeat = 30; public boolean autoAck; protected String inputPath; protected ConnectionFactory connectionFactory; protected Connection con; protected Channel channel; protected Sink sink; protected String exchangeType; protected boolean exchangeDurable; protected boolean queueDurable; @SuppressWarnings("unchecked") @Override public void init(Queue q, Input input, String inputPath) throws Exception { super.init(q, input, inputPath); this.inputPath = inputPath; if (input.getUrl() == null) throw new IllegalArgumentException("url is required"); if (queue != null && queue.length() == 0) queue = null; if (exchange != null && exchange.length() == 0) exchange = null; if (queue == null) { if (exchange == null) throw new IllegalArgumentException("queue is required"); } else { String[] toks = queue.split("[\\s]*#[\\s]*"); queue = toks[0]; queueDurable = toks.length == 1 || "true".equals(toks[1]); if (queue.length() == 0) throw new IllegalArgumentException("empty queue name"); } if (exchange != null) { String[] toks = exchange.split("[\\s]*#[\\s]*"); exchange = toks[0]; exchangeType = toks.length >= 2 ? toks[1] : "fanout"; exchangeDurable = toks.length < 3 || "true".equals(toks[2]); } connectionFactory = new ConnectionFactory(); connectionFactory.setUri(input.getUrl()); connectionFactory.setRequestedHeartbeat(heartbeat); ensureChannel(); } @Override public void start(final Sink sink) throws Exception { this.sink = sink; final Channel c = ensureChannel(); c.basicConsume(queue, autoAck, "qdb:" + inputPath, new DefaultConsumer(c) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { boolean ok = false; try { sink.append(envelope.getRoutingKey(), body); if (!autoAck) c.basicAck(envelope.getDeliveryTag(), false); ok = true; } catch (Exception e) { sink.error(e); } finally { if (!ok) { try { // todo should probably sit on the message for a bit before nacking to prevent storm c.basicNack(envelope.getDeliveryTag(), false, true); } catch (IOException e) { log.debug("Error nacking message: " + e, e); } } } } }); } @Override public void close() throws IOException { // this method cannot be synchronized or we get deadlock with shutdownCompleted if (con != null) con.close(); } protected synchronized Channel ensureChannel() throws Exception { if (channel == null) { try { con = connectionFactory.newConnection(); } catch (ConnectException e) { throw new ExpectedIOException(e.getMessage() + ": " + getConnectionInfo()); } channel = con.createChannel(); if (log.isInfoEnabled()) log.info(inputPath + ": Connected to " + getConnectionInfo()); channel.addShutdownListener(this); initChannel(channel); } return channel; } protected void initChannel(Channel channel) throws IOException { channel.queueDeclare(queue, queueDurable, false, false, null); if (exchange != null) { channel.exchangeDeclare(exchange, exchangeType, exchangeDurable); channel.queueBind(queue, exchange, routingKey); } } @Override public synchronized void shutdownCompleted(ShutdownSignalException cause) { channel = null; if (!cause.isInitiatedByApplication()) { try { if (con.isOpen()) con.close(); } catch (Exception ignore) { } con = null; sink.error("Channel closed unexpectedly: " + cause.getMessage(), null); } } protected String getConnectionInfo() { if (connectionFactory == null) return "(null)"; String host = connectionFactory.getHost(); if (host.length() == 0) host = "mac-subs-009"; return "amqp" + (connectionFactory.isSSL() ? "s" : "") + "://" + connectionFactory.getUsername() + "@" + host + ":" + connectionFactory.getPort() + connectionFactory.getVirtualHost(); } }