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

de.javakaffee.simplequeue.RichBDBQueue Maven / Gradle / Ivy

The newest version!
/*
 * 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 de.javakaffee.simplequeue;

import java.io.File;
import java.io.IOException;

import javax.annotation.Nullable;

import org.codehaus.jackson.map.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A wrapper around {@link BDBQueue} that provides json serialization and
 * blocking queue semantics.
 *
 * Created on Jun 22, 2011
 *
 * @author Martin Grotzke (initial creation)
 */
public class RichBDBQueue {

    private static final Logger LOG = LoggerFactory.getLogger(RichBDBQueue.class);

    private final ObjectMapper _mapper;
    private final BDBQueue _q;
    private final Class _type;
    private long pauseTimeInMillis = 100;

    private volatile boolean _doRun = true;
    private final Object _monitor = new Object();

    /**
     * Constructs the queue using the given file for persistence.
     *
     * @param queueBaseDir   queue database environment directory path
     * @param queueName      descriptive queue name
     * @param cacheSize      how often to sync the queue to disk
     *
     * @throws IOException thrown when the given file does not exist or is not writeable.
     */
    public RichBDBQueue(final File queueBaseDir, final String queueName, final int cacheSize, final Class type) throws IOException {
        _mapper = createObjectMapper();
        if (!_mapper.canSerialize(type)) {
            throw new IllegalArgumentException("The given type cannot be serialized by jackson (checked with new ObjectMapper().canSerialize(type)).");
        }
        _q = new BDBQueue(queueBaseDir.getAbsolutePath(), queueName, cacheSize);
        _type = type;
    }

    /**
     * Create the {@link ObjectMapper} used for serializing.
     * @return the configured {@link ObjectMapper}.
     */
    protected ObjectMapper createObjectMapper() {
        return new ObjectMapper();
    }

    /**
     * Serializes the given object and adds the serialized form to the queue file.
     * @param object the object to add.
     * @see BDBQueue#push(byte[])
     */
    public void push(final T object) throws IOException {
        final byte[] data = _mapper.writeValueAsBytes(object);
        _q.push(data);
        synchronized(_monitor) {
            _monitor.notifyAll();
        }
    }

    /**
     * Consume the queue using the given consumer. This method will run forever and feed the
     * consumer with new elements read from the queue. An element is removed from the queue
     * after it was consumed successfully (by {@link Consumer#consume(Object)}). If the consumer
     * fails consuming it, the element will try again. Therefore, if you can't process the element
     * but want that it's removed, just handle your exception accordingly and don't throw an
     * exception.
     * @param consumer the consumer used for handling queue elements.
     * @throws IOException
     * @throws InterruptedException
     */
    public void consume(final Consumer consumer) throws IOException, InterruptedException {
        while(_doRun) {
            final boolean queueEmpty;
            synchronized(_q) {
                queueEmpty = !processMessage(consumer);
            }
            if(queueEmpty) {
                synchronized(_monitor) {
                    _monitor.wait();
                }
            }
        }
    }

    /**
     * Process the head of the queue. Must return true if the queue was not empty.
     */
    private boolean processMessage(final Consumer consumer) throws IOException {
        final byte[] data = _q.peek();
        final T item = deserialize(data);
        if (item != null) {
            try {
                final boolean success = consumer.consume(item);
                if (success) {
                    _q.remove();
                }
            } catch(final RuntimeException e) {
            	// If we got interrupted we want to exit, so that this client/consumer also is told to exit
            	if(Thread.currentThread().isInterrupted()) {
                    LOG.error("Consumer got interrupted, exiting / rethrowing exception.", e);
                    throw e;
            	}
                LOG.error("Consumer could not read object.", e);
            }
        }
        return data != null;
    }

    public void clear() {
        _q.clear();
    }

    /**
     * Close the queue and release the thread processing {@link #consume(Consumer)} is there is any.
     */
    public void close() {
        synchronized(_q) {
            _doRun = false;
            synchronized(_monitor) {
                _monitor.notifyAll();
            }
            LOG.info("Closing queue.");
            _q.close();
        }
    }

    public boolean isEmpty() {
        return _q.isEmpty();
    }

    public T peek() throws IOException {
        final byte[] data = _q.peek();
        return deserialize(data);
    }

    public void remove() {
        _q.remove();
    }

    public long size() {
        return _q.size();
    }

    private T deserialize(@Nullable final byte[] data) {
        if (data == null) {
            return null;
        }
        try {
            return _mapper.readValue(data, 0, data.length, _type);
        } catch(final Exception e) {
            LOG.error("Element could not be deserialized, item will be removed. Data: {}", new Object[]{data}, e);
            _q.remove();
            return null;
        }
    }

    /**
     * The time in millis that {@link #consume(Consumer)} goes to sleep when the queue
     * is empty before checking again.
     * @return the pauseTimeInMillis
     */
    public long getPauseTimeInMillis() {
        return pauseTimeInMillis;
    }

    /**
     * Sets the time in millis that {@link #consume(Consumer)} goes to sleep when the queue
     * is empty before checking again.
     * @param pauseTimeInMillis the pauseTimeInMillis to set
     */
    public void setPauseTimeInMillis(final long pauseTimeInMillis) {
        this.pauseTimeInMillis = pauseTimeInMillis;
    }

    /**
     * For testing only.
     */
    BDBQueue getQ() {
        return _q;
    }

    /**
     * For testing only.
     */
    ObjectMapper getMapper() {
        return _mapper;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy