/*
* Copyright 2009 VoidSearch.com
*
* 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.voidsearch.voidbase.storage.queuetree;
import com.voidsearch.voidbase.storage.queuetree.persistence.FilesystemQueuePersistence;
import com.voidsearch.voidbase.storage.queuetree.persistence.QueuePersistence;
import com.voidsearch.voidbase.storage.queuetree.persistence.QueuePersistenceFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.*;
import com.voidsearch.voidbase.storage.SupervisedStorage;
import com.voidsearch.voidbase.storage.StorageOperation;
import com.voidsearch.voidbase.supervision.SupervisionException;
import com.voidsearch.voidbase.supervision.StorageStats;
import com.voidsearch.voidbase.apps.queuetree.protocol.QueueTreeProtocol;
import com.voidsearch.voidbase.serialization.VoidBaseSerialization;
public class QueueTreeStorage implements SupervisedStorage {
protected static final Logger logger = LoggerFactory.getLogger(QueueTreeStorage.class.getName());
protected static QueueTreeStorage singleton = null;
// storage structure
private ConcurrentHashMap<String, ArrayBlockingQueue> queueTree =
new ConcurrentHashMap<String, ArrayBlockingQueue>();
private ConcurrentHashMap<String, QueueMetadata> queueMedatada =
new ConcurrentHashMap<String, QueueMetadata>();
private ConcurrentHashMap<String, QueuePersistence> queuePersistence =
new ConcurrentHashMap<String, QueuePersistence>();
// maintenance support
private HashSet<StorageOperation> opLocks = new HashSet<StorageOperation>();
private long memorySize = 0;
// stats counters
private ConcurrentHashMap<StorageOperation, Long> queryCounter = new ConcurrentHashMap<StorageOperation, Long>();
private long totalQueries = 0;
// instantiation
protected QueueTreeStorage() { super(); }
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
public static synchronized QueueTreeStorage factory() {
if (singleton == null) {
singleton = new QueueTreeStorage();
}
return singleton;
}
/**
* create persisted queue
*
* @param queueName
* @param size
* @throws QueueAlreadyExistsException
* @throws SupervisionException
*/
public void createQueue(String queueName, int size) throws QueueAlreadyExistsException, SupervisionException {
createQueue(queueName, size, true);
}
/**
* create queue with defined persistence
*
* @param queueName
* @param size
* @throws QueueAlreadyExistsException
* @throws SupervisionException
*/
public void createQueue(String queueName, int size, boolean persisted) throws QueueAlreadyExistsException, SupervisionException {
updateQueryCounters(StorageOperation.CREATE);
if (opLocks.contains(StorageOperation.CREATE))
throw new SupervisionException();
if (!queueTree.containsKey(queueName)) {
queueTree.put(queueName,new ArrayBlockingQueue(size));
queueMedatada.put(queueName,new QueueMetadata(queueName,size));
queuePersistence.put(queueName, QueuePersistenceFactory.getPersistence(queueName));
}
else {
throw new QueueAlreadyExistsException();
}
}
// resize existing queue
public void resizeQueue(String queueName, int size) throws InvalidQueueException,SupervisionException {
updateQueryCounters(StorageOperation.RESIZE);
if (opLocks.contains(StorageOperation.RESIZE))
throw new SupervisionException();
if (opLocks.contains(StorageOperation.CREATE))
throw new SupervisionException();
if (queueTree.containsKey(queueName)) {
ArrayBlockingQueue queue = new ArrayBlockingQueue(size,true,queueTree.get(queueName));
queueTree.put(queueName,queue);
if (queueMedatada.containsKey(queueName)) {
QueueMetadata metadata = queueMedatada.get(queueName);
metadata.set(QueueTreeProtocol.SIZE,size);
queueMedatada.put(queueName,metadata);
}
}
else {
throw new InvalidQueueException();
}
}
// check if queue exists
public boolean queueExists(String name) throws SupervisionException {
updateQueryCounters(StorageOperation.EXISTS);
if (opLocks.contains(StorageOperation.EXISTS))
throw new SupervisionException();
return queueTree.containsKey(name);
}
// delete queue
public void deleteQueue(String queueName) throws InvalidQueueException,SupervisionException {
updateQueryCounters(StorageOperation.DELETE);
if (opLocks.contains(StorageOperation.DELETE))
throw new SupervisionException();
// remove queue
if (queueTree.containsKey(queueName)) {
decrementMemorySize(queueTree.get(queueName));
queueTree.remove(queueName);
}
// remove metadata
if (queueMedatada.containsKey(queueName)) {
decrementMemorySize(queueMedatada.get(queueName));
queueMedatada.remove(queueName);
}
else {
throw new InvalidQueueException();
}
}
// delete all queues
public void deleteAll() throws SupervisionException {
updateQueryCounters(StorageOperation.DELETE);
if (opLocks.contains(StorageOperation.DELETE))
throw new SupervisionException();
for (String queueName : queueTree.keySet()) {
try {
deleteQueue(queueName);
} catch (InvalidQueueException e) {
e.printStackTrace();
}
}
}
// flush single queue
// (high-order operation)
public void flushQueue(String queueName) throws SupervisionException {
if (opLocks.contains(StorageOperation.FLUSH))
throw new SupervisionException();
decrementMemorySize(queueTree.get(queueName));
ArrayBlockingQueue queue = queueTree.get(queueName);
try {
deleteQueue(queueName);
createQueue(queueName,(queue.size()+queue.remainingCapacity()));
} catch (InvalidQueueException e) {
e.printStackTrace();
} catch (QueueAlreadyExistsException e) {
e.printStackTrace();
}
}
// flushAll all queues
// (high-order operation)
public void flushAll() throws SupervisionException {
updateQueryCounters(StorageOperation.FLUSH);
if (opLocks.contains(StorageOperation.FLUSH))
throw new SupervisionException();
for (String queueName : queueTree.keySet())
flushQueue(queueName);
}
public LinkedHashMap<String, Object> getMetadataMap(String queueName) {
if (queueMedatada.containsKey(queueName)) {
QueueMetadata metadata = queueMedatada.get(queueName);
return metadata.getMetadata();
}
else {
return new LinkedHashMap<String, Object>();
}
}
// push a element in given queue
public void putFIFO(String queueName, Object value) throws InvalidQueueException,SupervisionException {
updateQueryCounters(StorageOperation.PUT);
if (opLocks.contains(StorageOperation.PUT))
throw new SupervisionException();
incrementMemorySize(value);
if (queueTree.containsKey(queueName)) {
ArrayBlockingQueue queue = queueTree.get(queueName);
if (!(value instanceof QueueMetadata)) {
// lazy types - first inserted element defines queue type
if (queueMedatada.containsKey(queueName)) {
QueueMetadata metadata = queueMedatada.get(queueName);
if (metadata.getType().equals(VoidBaseSerialization.UNKNOWN))
metadata.setType(VoidBaseSerialization.getType(value));
}
}
synchronized (queue) {
if (queue.remainingCapacity() == 0) {
try {
decrementMemorySize(queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.add(new QueueEntry(value));
if (!(value instanceof QueueMetadata)) {
// update metadata with hooks
QueueTreeHooks.handlePut(queueMedatada.get(queueName),queue,value);
}
// a bit of controlled recursion
putFIFO(queueName + "_metadata", new QueueMetadata(queueMedatada.get(queueName)));
// persist update
if (true) {
try {
QueuePersistence persistenceEngine = queuePersistence.get(queueName);
persistenceEngine.add(queueName, new QueueEntry(value), new QueueMetadata(queueMedatada.get(queueName)));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
else {
// handle missing metadata queue gracefull
if (!(value instanceof QueueMetadata)) {
throw new InvalidQueueException();
}
}
}
public List getFIFO(String queueName,int count) throws InvalidQueueException, SupervisionException {
return getFIFO(queueName,count,0);
}
// retrieve a number of fifo elements element from given queue
public List getFIFO(String queueName,int count, long timestamp) throws InvalidQueueException, SupervisionException {
updateQueryCounters(StorageOperation.GET);
if (opLocks.contains(StorageOperation.GET))
throw new SupervisionException();
if (queueTree.containsKey(queueName)) {
LinkedList resultList = new LinkedList();
ArrayBlockingQueue queue = queueTree.get(queueName);
Iterator it = queue.iterator();
while (it.hasNext()) {
QueueEntry entry = (QueueEntry)it.next();
if (entry.getTimestamp() > timestamp) {
resultList.add(entry);
}
}
Collections.reverse(resultList);
int limit = (count < resultList.size()) ? count : resultList.size();
return resultList.subList(0, limit);
}
else {
throw new InvalidQueueException();
}
}
// list all registered queues
public LinkedList listQueues() throws SupervisionException {
updateQueryCounters(StorageOperation.LIST);
if (opLocks.contains(StorageOperation.LIST))
throw new SupervisionException();
LinkedList<String> queueList = new LinkedList<String>();
for (String queue : queueTree.keySet()) {
queueList.add(queue);
}
return queueList;
}
// supervision support
public void blockOperation(StorageOperation op) throws SupervisionException {
opLocks.add(op);
}
public void enableOperation(StorageOperation op) throws SupervisionException {
opLocks.remove(op);
}
public void updateStats(StorageStats stats) {
stats.setCounter(StorageStats.Counter.SIZE,getSize());
stats.setCounter(StorageStats.Counter.TOTAL_ENTRIES,getTotalEntries());
stats.setCounter(StorageStats.Counter.MEMORY_USAGE,getMemorySize());
stats.setCounter(StorageStats.Counter.QUERY_COUNT,getTotalQueries());
}
// util methods
public void updateQueryCounters() {
updateQueryCounters(null);
}
public void updateQueryCounters(StorageOperation op) {
totalQueries++;
if (queryCounter.containsKey(op)){
queryCounter.put(op, queryCounter.get(op)+1);
}
else {
queryCounter.put(op,(long)1);
}
}
public long getSize() {
return queueTree.size();
}
public long getTotalEntries() {
int cnt = 0;
for (String entry: queueTree.keySet()) {
cnt += queueTree.get(entry).size();
}
return cnt;
}
public long getTotalQueries() {
return totalQueries;
}
public void incrementMemorySize(Object obj) {
synchronized (QueueTreeStorage.class) {
memorySize += getMemorySize(obj);
}
}
public void decrementMemorySize(Object obj) {
synchronized (QueueTreeStorage.class) {
memorySize -= getMemorySize(obj);
}
}
public static long getMemorySize(Object obj) {
if (obj instanceof String) {
String str = (String)obj;
return str.getBytes().length;
}
else if (obj instanceof QueueEntry) {
QueueEntry entry = (QueueEntry)obj;
return getMemorySize(entry.getValue());
}
else if (obj instanceof QueueMetadata) {
QueueMetadata entry = (QueueMetadata)obj;
return getMemorySize(entry.getMetadata());
}
else if (obj instanceof ArrayBlockingQueue) {
ArrayBlockingQueue queue = (ArrayBlockingQueue)obj;
Iterator it = queue.iterator();
long total = 0;
while (it.hasNext()) {
total += getMemorySize(it.next());
}
return total;
}
else if (obj instanceof HashMap) {
HashMap map = (HashMap)obj;
Iterator it = map.keySet().iterator();
long total = 0;
while (it.hasNext()) {
total += getMemorySize(map.get(it.next()));
}
return total;
}
return 0;
}
public long getMemorySize() {
return memorySize;
}
}