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

net.java.trueupdate.jms.JmsReceiver Maven / Gradle / Ivy

There is a newer version: 0.8.1
Show newest version
/*
 * Copyright (C) 2013 Schlichtherle IT Services & Stimulus Software.
 * All rights reserved. Use is subject to license terms.
 */
package net.java.trueupdate.jms;

import java.util.concurrent.*;
import javax.annotation.*;
import javax.annotation.concurrent.ThreadSafe;
import javax.jms.*;
import net.java.trueupdate.message.UpdateMessageListener;
import static net.java.trueupdate.util.Objects.requireNonNull;
import net.java.trueupdate.util.builder.AbstractBuilder;

/**
 * Receives JMS messages in a loop and forwards them to the given
 * {@link MessageListener}.
 * This {@link Runnable} should be run by a {@link Thread} in environments
 * where it's not possible to use
 * {@link Session#setMessageListener(MessageListener)}, e.g. in a web app
 * or an enterprise app, and it's not desirable to use a message driven
 * bean.
 *
 * @author Christian Schlichtherle
 */
@ThreadSafe
public final class JmsReceiver implements Runnable {

    public static final ThreadFactory LISTENER_THREAD_FACTORY =
            new ThreadFactory() {
                @Override public Thread newThread(Runnable r) {
                    return new Thread(r, "TrueUpdate JMS / Listener");
                }
            };

    private static final boolean NO_LOCAL = true;

    private final Object lock = new Object();
    private final @Nullable String subscriptionName;
    private final @CheckForNull String messageSelector;
    private final MessageListener messageListener;
    private final Destination destination;
    private final ConnectionFactory connectionFactory;
    private final ExecutorService executorService;

    private Connection connection;

    // Work around https://java.net/jira/browse/GLASSFISH-20836 .
    private volatile Thread thread;

    private JmsReceiver(final Builder b) {
        this.subscriptionName = b.subscriptionName;
        this.messageSelector = b.messageSelector;
        this.messageListener = requireNonNull(b.messageListener);
        this.destination = requireNonNull(b.destination);
        this.connectionFactory = requireNonNull(b.connectionFactory);
        this.executorService = requireNonNull(b.executorService);
    }

    /** Returns a new builder for JMS receivers. */
    public static Builder builder() { return new Builder(); }

    @Override public void run() {
        try {
            Connection c = null;
            try {
                MessageConsumer mc;
                synchronized (lock) {
                    c = connection;
                    if (null != c)
                        throw new java.lang.IllegalStateException("Already running.");
                    c = connectionFactory.createConnection();
                    final Session s = c.createSession(false, Session.CLIENT_ACKNOWLEDGE);
                    final Destination d = destination;
                    mc = d instanceof Topic
                            ? s.createDurableSubscriber((Topic) d, subscriptionName, messageSelector, NO_LOCAL)
                            : s.createConsumer(d, messageSelector);
                    thread = Thread.currentThread();
                    connection = c;
                }
                c.start();
                while (true) {
                    final Message m = mc.receive();
                    if (null == m) break;
                    synchronized(lock) {
                        if (null == connection) break;
                        m.acknowledge();
                        executorService.execute(new Runnable() {
                            @Override public void run() {
                                messageListener.onMessage(m);
                            }
                        });
                    }
                }
            } finally {
                if (null != c) {
                    // c.close(); // see https://issues.apache.org/jira/browse/AMQ-4769 .
                    synchronized (lock) {
                        thread = null;
                        lock.notifyAll();
                    }
                }
            }
        } catch (JMSException ex) {
            throw new java.lang.IllegalStateException(ex);
        }
    }

    /** Stops this runnable from a different thread. */
    public void stop(long timeout, TimeUnit unit) throws Exception {
        // HC SVNT DRACONIS
        // The following code is a fucking mess, but that's just because the
        // JMS implementations Apache ActiveMQ 5.8.0 and Open MQ 5.0 are
        // riddled with bugs - SCNR!

        final long stop = System.currentTimeMillis() + unit.toMillis(timeout);
        unit = TimeUnit.MILLISECONDS;

        synchronized (lock) {
            final Connection c = connection;
            if (null == c) return;
            connection = null;

            // Work around https://java.net/jira/browse/GLASSFISH-20836 .
            executorService.submit(new Runnable() {
                @SuppressWarnings("SleepWhileInLoop")
                @Override public void run() {
                    for (Thread t; null != (t = thread); ) {
                        // Wait first to give c.close() priority and avoid a
                        // JMSException with a wrapped InterruptedException
                        // when using ActiveMQ 5.8.0.
                        try { Thread.sleep(100); }
                        catch (InterruptedException stop) { }
                        t.interrupt();
                    }
                }
            });

            try {
                c.close();
            } finally {
                try {
                    timeout = Math.max(1, stop - System.currentTimeMillis());
                    lock.wait(timeout);
                    if (null != thread)
                        throw new TimeoutException();
                } finally {
                    if (0 != executorService.shutdownNow().size())
                        throw new AssertionError();
                    timeout = Math.max(1, stop - System.currentTimeMillis());
                    if (!executorService.awaitTermination(timeout, unit))
                        throw new TimeoutException();
                }
            }
        }
    }

    /**
     * A builder for a JMS receiver.
     *
     * @param 

The type of the parent builder, if defined. */ @SuppressWarnings("PackageVisibleField") public static class Builder

extends AbstractBuilder

{ @CheckForNull ConnectionFactory connectionFactory; @CheckForNull Destination destination; @CheckForNull String messageSelector, subscriptionName; @CheckForNull MessageListener messageListener; @CheckForNull ExecutorService executorService; protected Builder() { } public final Builder

connectionFactory( final @Nullable ConnectionFactory connectionFactory) { this.connectionFactory = connectionFactory; return this; } public final Builder

destination( final @Nullable Destination destination) { this.destination = destination; return this; } public final Builder

subscriptionName( final @Nullable String subscriptionName) { this.subscriptionName = subscriptionName; return this; } public final Builder

messageSelector( final @Nullable String messageSelector) { this.messageSelector = messageSelector; return this; } public final Builder

messageListener( final @Nullable MessageListener messageListener) { this.messageListener = messageListener; return this; } public final Builder

updateMessageListener( final @CheckForNull UpdateMessageListener updateMessageListener) { this.messageListener = null == updateMessageListener ? null : JmsListener.create(updateMessageListener); return this; } public final Builder

executorService( final @Nullable ExecutorService executorService) { this.executorService = executorService; return this; } @Override public final JmsReceiver build() { return new JmsReceiver(this); } } // Builder }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy