at.molindo.notify.dispatch.PollingPushDispatcher.java Source code

Java tutorial

Introduction

Here is the source code for at.molindo.notify.dispatch.PollingPushDispatcher.java

Source

/**
 * Copyright 2010 Molindo GmbH
 *
 * 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 at.molindo.notify.dispatch;

import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.SmartLifecycle;

import at.molindo.notify.INotifyService;
import at.molindo.notify.INotifyService.NotifyRuntimeException;
import at.molindo.notify.model.Notification;
import at.molindo.notify.util.AbstractSmartLifecycle;
import at.molindo.utils.concurrent.FactoryThread;
import at.molindo.utils.concurrent.FactoryThread.FactoryThreadGroup;
import at.molindo.utils.concurrent.KeyLock;
import at.molindo.utils.concurrent.KeyLock.KeyLockedException;

public class PollingPushDispatcher extends AbstractPushDispatcher
        implements INotifyService.INotificationListner, DisposableBean, SmartLifecycle {

    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(PollingPushDispatcher.class);

    private static final int DEFAULT_POOL_SIZE = 1;

    private int _poolSize = DEFAULT_POOL_SIZE;

    private INotifyService _notifyService;

    private final Object _wait = new Object();
    private final KeyLock<Long, Void> _notificationLock = KeyLock.newKeyLock(false);

    private FactoryThreadGroup _threadGroup;

    private final Lifecycle _lifecycle = new Lifecycle();

    @Override
    public void afterPropertiesSet() {
        super.afterPropertiesSet();
        _threadGroup = new FactoryThread.FactoryThreadGroup(PollingPushDispatcher.class.getSimpleName(), _poolSize,
                new FactoryThread.IRunnableFactory() {

                    @Override
                    public Runnable newRunnable() {
                        return new Polling();
                    }
                });

        _notifyService.addNotificationListener(this);
    }

    @Override
    public void destroy() {
        stop();
        _notifyService.removeNotificationListener(this);
    }

    @Override
    public void notification(Notification notification) {
        synchronized (_wait) {
            _wait.notify();
        }
    }

    public void setNotifyService(INotifyService notifyService) {
        _notifyService = notifyService;
    }

    public int getPoolSize() {
        return _poolSize;
    }

    public void setPoolSize(int poolSize) {
        _poolSize = poolSize;
    }

    class Polling implements Runnable {
        @Override
        public void run() {
            Notification notification = getNotificationDAO().getNext();
            if (notification != null) {
                doPush(notification);
            } else {
                // add a polling delay
                delay();
            }
        }

        /**
         * wraps a {@link KeyLock} around {@link #push(DispatchConf)}
         * 
         * @see #doPush(DispatchConf)
         * @return null if notification already gets pushed by another thread
         */
        @CheckForNull
        private void doPush(final @Nonnull Notification notification) {
            try {
                _notificationLock.withLock(notification.getId(), new Callable<Void>() {

                    @Override
                    public Void call() {
                        dispatch(notification);
                        return null;
                    }
                });
            } catch (KeyLockedException e) {
                /*
                 * INotificationDAO implementation should avoid this but it
                 * might happen if a push takes an unusual amount of time to
                 * finish
                 */
                log.info("notification id currently locked: " + e.getKey());
            } catch (Exception e) {
                throw new NotifyRuntimeException("unexepcted exception from doPush()", e);
            }
        }

        /**
         * overwrite for testing
         */
        protected void delay() {
            synchronized (_wait) {
                try {
                    _wait.wait(TimeUnit.SECONDS.toMillis(20));
                } catch (InterruptedException e) {
                    log.debug("polling thread interrupted", e);
                }
            }
        }

    }

    @Override
    public void start() {
        _lifecycle.start();
    }

    @Override
    public void stop() {
        _lifecycle.stop();
    }

    @Override
    public boolean isRunning() {
        return _lifecycle.isRunning();
    }

    @Override
    public int getPhase() {
        return _lifecycle.getPhase();
    }

    @Override
    public boolean isAutoStartup() {
        return _lifecycle.isAutoStartup();
    }

    @Override
    public void stop(Runnable callback) {
        _lifecycle.stop(callback);
    }

    private class Lifecycle extends AbstractSmartLifecycle {

        private volatile boolean _running = false;

        @Override
        public boolean isRunning() {
            return _running;
        }

        @Override
        protected void doStart() {
            _threadGroup.start();
            _running = true;
        }

        @Override
        protected void doStop() {
            _threadGroup.setInactive();
            _running = false;
            synchronized (_wait) {
                _wait.notifyAll();
            }
            try {
                log.info("waiting for termination of running notification tasks");
                _threadGroup.join();
                log.info("all running notification tasks terminated");
            } catch (InterruptedException e1) {
                log.warn("interrupted while waiting for termination of notificaiton tasks");
            }
        }

    }
}