org.springframework.integration.store.MessageGroupQueue.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.integration.store.MessageGroupQueue.java

Source

/*
 * Copyright 2002-2012 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.springframework.integration.store;

import java.util.AbstractQueue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.integration.Message;
import org.springframework.util.Assert;

/**
 * A {@link BlockingQueue} that is backed by a {@link MessageGroupStore}. Can be used to ensure guaranteed delivery in
 * the face of transaction rollback (assuming the store is transactional) and also to ensure messages are not lost if
 * the process dies (assuming the store is durable). To use the queue across process re-starts, the same group id
 * must be provided, so it needs to be unique but identifiable with a single logical instance of the queue.
 *
 * @author Dave Syer
 * @author Oleg Zhurakousky
 * @author Gunnar Hillert
 *
 * @since 2.0
 *
 */
public class MessageGroupQueue extends AbstractQueue<Message<?>> implements BlockingQueue<Message<?>> {

    private final Log logger = LogFactory.getLog(getClass());

    private static final int DEFAULT_CAPACITY = Integer.MAX_VALUE;

    private final MessageGroupStore messageGroupStore;

    private final Object groupId;

    private final int capacity;

    //This one could be a global semaphore
    private final Lock storeLock;

    private final Condition messageStoreNotFull;

    private final Condition messageStoreNotEmpty;

    public MessageGroupQueue(MessageGroupStore messageGroupStore, Object groupId) {
        this(messageGroupStore, groupId, DEFAULT_CAPACITY, new ReentrantLock(true));
    }

    public MessageGroupQueue(MessageGroupStore messageGroupStore, Object groupId, int capacity) {
        this(messageGroupStore, groupId, capacity, new ReentrantLock(true));
    }

    public MessageGroupQueue(MessageGroupStore messageGroupStore, Object groupId, Lock storeLock) {
        this(messageGroupStore, groupId, DEFAULT_CAPACITY, storeLock);
    }

    public MessageGroupQueue(MessageGroupStore messageGroupStore, Object groupId, int capacity, Lock storeLock) {
        Assert.isTrue(capacity > 0, "'capacity' must be greater than 0");
        Assert.notNull(storeLock, "'storeLock' must not be null");
        Assert.notNull(messageGroupStore, "'messageGroupStore' must not be null");
        Assert.notNull(groupId, "'groupId' must not be null");
        this.storeLock = storeLock;
        this.messageStoreNotFull = this.storeLock.newCondition();
        this.messageStoreNotEmpty = this.storeLock.newCondition();
        this.messageGroupStore = messageGroupStore;
        this.groupId = groupId;
        this.capacity = capacity;
    }

    public Iterator<Message<?>> iterator() {
        return getMessages().iterator();
    }

    public int size() {
        return messageGroupStore.messageGroupSize(groupId);
    }

    public Message<?> peek() {
        Message<?> message = null;
        final Lock storeLock = this.storeLock;
        try {
            storeLock.lockInterruptibly();
            try {
                Collection<Message<?>> messages = getMessages();
                if (!messages.isEmpty()) {
                    message = messages.iterator().next();
                }
            } finally {
                storeLock.unlock();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return message;
    }

    public Message<?> poll(long timeout, TimeUnit unit) throws InterruptedException {
        Message<?> message = null;
        long timeoutInNanos = unit.toNanos(timeout);
        final Lock storeLock = this.storeLock;
        storeLock.lockInterruptibly();

        try {
            while (this.size() == 0 && timeoutInNanos > 0) {
                timeoutInNanos = this.messageStoreNotEmpty.awaitNanos(timeoutInNanos);
            }
            message = this.doPoll();

        } finally {
            storeLock.unlock();
        }
        return message;
    }

    public Message<?> poll() {
        Message<?> message = null;
        final Lock storeLock = this.storeLock;
        try {
            storeLock.lockInterruptibly();
            try {
                message = this.doPoll();
            } finally {
                storeLock.unlock();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return message;
    }

    public int drainTo(Collection<? super Message<?>> c) {
        return this.drainTo(c, Integer.MAX_VALUE);
    }

    public int drainTo(Collection<? super Message<?>> collection, int maxElements) {
        Assert.notNull(collection, "'collection' must not be null");
        int originalSize = collection.size();
        ArrayList<Message<?>> list = new ArrayList<Message<?>>();
        final Lock storeLock = this.storeLock;
        try {
            storeLock.lockInterruptibly();
            try {
                Message<?> message = this.messageGroupStore.pollMessageFromGroup(groupId);
                for (int i = 0; i < maxElements && message != null; i++) {
                    list.add(message);
                    message = this.messageGroupStore.pollMessageFromGroup(groupId);
                }
                this.messageStoreNotFull.signal();
            } finally {
                storeLock.unlock();
            }
        } catch (InterruptedException e) {
            logger.warn("Queue may not have drained completely since this operation was interrupted", e);
            Thread.currentThread().interrupt();
        }
        collection.addAll(list);
        return collection.size() - originalSize;
    }

    public boolean offer(Message<?> message) {
        boolean offered = true;
        final Lock storeLock = this.storeLock;
        try {
            storeLock.lockInterruptibly();
            try {
                offered = this.doOffer(message);
            } finally {
                storeLock.unlock();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return offered;
    }

    public boolean offer(Message<?> message, long timeout, TimeUnit unit) throws InterruptedException {
        long timeoutInNanos = unit.toNanos(timeout);
        boolean offered = false;

        final Lock storeLock = this.storeLock;
        storeLock.lockInterruptibly();
        try {
            if (capacity != Integer.MAX_VALUE) {
                while (this.size() == capacity && timeoutInNanos > 0) {
                    timeoutInNanos = this.messageStoreNotFull.awaitNanos(timeoutInNanos);
                }
            }
            if (timeoutInNanos > 0) {
                offered = this.doOffer(message);
            }
        } finally {
            storeLock.unlock();
        }
        return offered;
    }

    public void put(Message<?> message) throws InterruptedException {
        final Lock storeLock = this.storeLock;
        storeLock.lockInterruptibly();
        try {
            if (capacity != Integer.MAX_VALUE) {
                while (this.size() == capacity) {
                    this.messageStoreNotFull.await();
                }
            }
            this.doOffer(message);
        } finally {
            storeLock.unlock();
        }
    }

    public int remainingCapacity() {
        if (capacity == Integer.MAX_VALUE) {
            return Integer.MAX_VALUE;
        }
        return capacity - this.size();
    }

    public Message<?> take() throws InterruptedException {
        Message<?> message = null;
        final Lock storeLock = this.storeLock;
        storeLock.lockInterruptibly();

        try {
            while (this.size() == 0) {
                this.messageStoreNotEmpty.await();
            }
            message = this.doPoll();

        } finally {
            storeLock.unlock();
        }
        return message;
    }

    private Collection<Message<?>> getMessages() {
        return messageGroupStore.getMessageGroup(groupId).getMessages();
    }

    /**
     * It is assumed that the 'storeLock' is being held by the caller, otherwise
     * IllegalMonitorStateException may be thrown
     */
    private Message<?> doPoll() {
        Message<?> message = this.messageGroupStore.pollMessageFromGroup(groupId);
        this.messageStoreNotFull.signal();
        return message;
    }

    /**
     * It is assumed that the 'storeLock' is being held by the caller, otherwise
     * IllegalMonitorStateException may be thrown
     */
    private boolean doOffer(Message<?> message) {
        boolean offered = false;
        if (capacity == Integer.MAX_VALUE || this.size() < capacity) {
            messageGroupStore.addMessageToGroup(groupId, message);
            offered = true;
            this.messageStoreNotEmpty.signal();
        }
        return offered;
    }
}