/*
* Copyright 2011-2014 Proofpoint, Inc.
*
* 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 com.proofpoint.event.collector.queue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.leansoft.bigqueue.BigQueueImpl;
import com.leansoft.bigqueue.IBigQueue;
import com.proofpoint.json.JsonCodec;
import com.proofpoint.log.Logger;
import com.proofpoint.reporting.Reported;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
public class FileBackedQueue<T> implements Queue<T>
{
private static final Logger log = Logger.get(FileBackedQueue.class);
private final IBigQueue queue;
private final JsonCodec<T> codec;
private final long capacity;
private final String name;
private final ScheduledFuture<?> cleanupThread;
private final AtomicLong itemsEnqueued = new AtomicLong(0);
private final AtomicLong itemsDequeued = new AtomicLong(0);
private final AtomicLong itemsDroppped = new AtomicLong(0);
public FileBackedQueue(String name, String dataDirectory, JsonCodec<T> codec, long capacity, ScheduledExecutorService executor)
throws IOException
{
this.codec = checkNotNull(codec, "codec is null");
checkArgument(name != null && !name.isEmpty(), "name cannot be null or empty");
checkNotNull(dataDirectory, "dataDirectory is null");
checkArgument(!dataDirectory.isEmpty(), "dataDirectory is empty");
checkArgument(capacity > 0, "capacity must be greater than zero");
// create data directory if it's missing
Path dataDirPath = Paths.get(dataDirectory);
Files.createDirectories(dataDirPath); // omitting FileAttributes parameter in order to avoid dealing with POSIX vs ACL file permissions
this.name = name;
this.queue = new BigQueueImpl(dataDirectory, name);
this.capacity = capacity;
cleanupThread = executor.scheduleAtFixedRate(new FileCleaner(), 1, 1, TimeUnit.MINUTES);
}
@Reported
public long getItemsEnqueued()
{
return itemsEnqueued.getAndSet(0); // Reset every time the metric is called
}
@Reported
public long getItemsDequeued()
{
return itemsDequeued.getAndSet(0); // Reset every time the metric is called
}
@Reported
public long getItemsDropped()
{
return itemsDroppped.getAndSet(0); // Reset every time the metric is called
}
@Override
@Reported
public long getSize()
{
return queue.size();
}
@Override
public boolean offer(T item)
throws IOException
{
checkNotNull(item, "item is null");
if (queue.size() < capacity) {
queue.enqueue(codec.toJson(item).getBytes("UTF-8"));
itemsEnqueued.getAndAdd(1);
return true;
}
else {
return false;
}
}
@Override
public boolean enqueueOrDrop(T item)
throws IOException
{
checkNotNull(item, "item is null");
if (queue.size() >= capacity) {
itemsDroppped.getAndAdd(1);
return false;
}
queue.enqueue(codec.toJson(item).getBytes("UTF-8"));
itemsEnqueued.getAndAdd(1);
return true;
}
@Override
public List<T> enqueueAllOrDrop(List<T> items)
throws IOException
{
checkNotNull(items, "items are null");
Builder<T> builder = new Builder<>();
for (T item : items) {
if (enqueueOrDrop(item)) {
builder.add(item);
}
}
return builder.build();
}
@Override
public List<T> dequeue(int numItems)
throws IOException
{
checkArgument(numItems > 0, "numItems must be greater than zero");
Builder<T> builder = new Builder<>();
int chunks = 0;
while (chunks < numItems) {
byte[] item = queue.dequeue();
if (item == null) {
break;
}
builder.add(codec.fromJson(item));
chunks++;
}
ImmutableList<T> items = builder.build();
itemsDequeued.getAndAdd(items.size());
return items;
}
@Override
public void close()
throws IOException
{
cleanupThread.cancel(false);
queue.close();
}
@Override
public void removeAll()
throws IOException
{
queue.removeAll();
}
@Override
public String getName()
{
return name;
}
ScheduledFuture<?> getCleanupThread()
{
return cleanupThread;
}
private class FileCleaner implements Runnable
{
@Override
public void run()
{
try {
queue.gc();
}
catch (Exception e) {
log.error(e, "Could not remove old queue files.");
}
}
}
}