/**
*
* Copyright 2004 Protique Ltd
*
* 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.activemq.io.util;
import java.io.File;
import java.io.IOException;
import java.util.List;
import javax.jms.JMSException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.activemq.io.WireFormat;
import org.activemq.io.impl.DefaultWireFormat;
import org.activemq.message.ActiveMQMessage;
import EDU.oswego.cs.dl.util.concurrent.SynchronizedInt;
/**
* Implements a controlled thread safe queue, with ActiveMQMessages being spooled to disk for reading asynchronously.
*/
public class SpooledBoundedActiveMQMessageQueue {
private String name;
private DataContainer container;
private WireFormat wireFormat;
private long maxDataLength;
private boolean closed;
private boolean stopped;
private SynchronizedInt size = new SynchronizedInt(0);
private Object inLock = new Object();
private Object outLock = new Object();
private static int WAIT_TIMEOUT = 250;
private static final Log log = LogFactory.getLog(SpooledBoundedActiveMQMessageQueue.class);
/**
* Constructor for SpooledBoundedActiveMQMessageQueue
*
* @param dir
* @param name
* @param maxDataLength
* @param maxBlockSize
* @throws IOException
*/
public SpooledBoundedActiveMQMessageQueue(File dir, String name, long maxDataLength, int maxBlockSize) throws IOException {
//ensure name can be used as a file name
char[] chars = name.toCharArray();
for (int i = 0;i < chars.length;i++) {
if (!Character.isLetterOrDigit(chars[i])) {
chars[i] = '_';
}
}
this.name = new String(chars);
this.maxDataLength = maxDataLength;
this.wireFormat = new DefaultWireFormat();
this.container = new DataContainer(dir, this.name, maxBlockSize);
//as the DataContainer is temporary, clean-up any old files
this.container.deleteAll();
}
/**
* Constructor for SpooledBoundedActiveMQMessageQueue
*
* @param dir
* @param name
* @throws IOException
*/
public SpooledBoundedActiveMQMessageQueue(File dir, String name) throws IOException {
this(dir, name, 1024 * 1024 * 64, 8192);
}
/**
* Place a ActiveMQMessage at the head of the Queue
*
* @param packet
* @throws JMSException
*/
public void enqueue(ActiveMQMessage packet) throws JMSException {
if (!isFull()) {
enqueueNoBlock(packet);
}
else {
synchronized (inLock) {
try {
while (isFull()) {
inLock.wait(WAIT_TIMEOUT);
}
}
catch (InterruptedException ie) {
}
}
enqueueNoBlock(packet);
}
}
/**
* Enqueue a ActiveMQMessage without checking usage limits
*
* @param packet
* @throws JMSException
*/
public void enqueueNoBlock(ActiveMQMessage packet) throws JMSException {
byte[] data;
try {
data = wireFormat.toBytes(packet);
size.increment();
container.write(data);
}
catch (IOException e) {
JMSException jmsEx = new JMSException("enqueNoBlock failed: " + e.getMessage());
jmsEx.setLinkedException(e);
throw jmsEx;
}
synchronized (outLock) {
outLock.notify();
}
}
/**
* @return the first dequeued ActiveMQMessage or blocks until one is available
* @throws JMSException
* @throws InterruptedException
*/
public ActiveMQMessage dequeue() throws JMSException, InterruptedException {
ActiveMQMessage result = null;
synchronized (outLock) {
while ((result = dequeueNoWait()) == null) {
outLock.wait(WAIT_TIMEOUT);
}
}
return result;
}
/**
* @return the ActiveMQMessage from the head of the Queue or null if the Queue is empty
* @param timeInMillis maximum time to wait to dequeue a ActiveMQMessage
* @throws JMSException
* @throws InterruptedException
*/
public ActiveMQMessage dequeue(long timeInMillis) throws JMSException, InterruptedException {
ActiveMQMessage result = dequeueNoWait();
if (result == null) {
synchronized (outLock) {
outLock.wait(timeInMillis);
result = dequeueNoWait();
}
}
return result;
}
/**
* @return the ActiveMQMessage from the head of the Queue or null if the Queue is empty
* @throws JMSException
* @throws InterruptedException
*/
public ActiveMQMessage dequeueNoWait() throws JMSException, InterruptedException {
ActiveMQMessage result = null;
if (stopped) {
synchronized (outLock) {
while (stopped && !closed) {
outLock.wait(WAIT_TIMEOUT);
}
}
}
byte[] data;
try {
data = container.read();
if (data != null) {
result = (ActiveMQMessage)wireFormat.fromBytes(data);
size.decrement();
}
}
catch (IOException e) {
JMSException jmsEx = new JMSException("fromBytes failed");
jmsEx.setLinkedException(e);
throw jmsEx;
}
if (result != null && !isFull()) {
synchronized (inLock) {
inLock.notify();
}
}
return result;
}
/**
* @return true if this queue has reached it's data length limit
*/
public boolean isFull() {
return container.length() >= maxDataLength;
}
/**
* close this queue
*/
public void close() {
try {
closed = true;
container.close();
}
catch (IOException ioe) {
log.warn("Couldn't close queue", ioe);
}
}
/**
* @return the name of this BoundedActiveMQMessageQueue
*/
public String getName() {
return name;
}
/**
* @return number of ActiveMQMessages held by this queue
*/
public int size() {
return size.get();
}
/**
* @return true if the queue is enabled for dequeing (default = true)
*/
public boolean isStarted() {
return stopped == false;
}
/**
* disable dequeueing
*/
public void stop() {
synchronized (outLock) {
stopped = true;
}
}
/**
* enable dequeueing
*/
public void start() {
stopped = false;
synchronized (outLock) {
outLock.notifyAll();
}
synchronized (inLock) {
inLock.notifyAll();
}
}
/**
* @return true if this queue is empty
*/
public boolean isEmpty() {
return size.get() == 0;
}
/**
* clear the queue
*/
public void clear() {
}
/**
* @return a copy of the contents
*/
public List getContents() {
return null;
}
}