/*
* Copyright 2014 mpowers
*
* 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.trsst.server;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.concurrent.ConcurrentMap;
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
/**
* A simple passthrough that caches read operations.
*
* @author mpowers
*/
public class CachingStorage implements Storage {
/**
* Persistent storage delegate used to fetch items not in cache and to
* passthrough write operations.
*/
private Storage persistentStorage;
/**
* Persistent storage delegate used to fetch items not in cache and to
* passthrough write operations.
*/
private ConcurrentMap<String, Object> cache;
/**
* Manages index and calls to the specified storage delegate to handle
* individual feed, entry, and resource persistence.
*
* @param delegate
* @throws IOException
*/
public CachingStorage(Storage delegate) throws IOException {
persistentStorage = delegate;
cache = new ConcurrentLinkedHashMap.Builder<String, Object>()
.maximumWeightedCapacity(256).build();
}
private static char DELIMITER = 0;
private static final String tokenize(Object... args) {
StringBuffer buf = new StringBuffer();
for (Object arg : args) {
buf.append(arg).append(DELIMITER);
}
return buf.toString();
}
private static Object NOT_FOUND = "NOT_FOUND";
private Object get(String token) {
if (cache.containsKey(token)) {
return cache.get(token);
}
return NOT_FOUND;
}
private void put(String token, Object value) {
cache.put(token, value);
}
private void purge(String prefix) {
// purge all keys with prefix
for (String key : cache.keySet()) {
if (key.startsWith(prefix)) {
cache.remove(key);
}
}
}
public String[] getFeedIds(int start, int length) {
String token = tokenize("getFeedIds", start, length);
Object result = get(token);
if (result == NOT_FOUND) {
result = persistentStorage.getFeedIds(start, length);
put(token, result);
}
return (String[]) result;
}
public String[] getCategories(int start, int length) {
String token = tokenize("getCategories", start, length);
Object result = get(token);
if (result == NOT_FOUND) {
result = persistentStorage.getCategories(start, length);
put(token, result);
}
return (String[]) result;
}
public int getEntryCount(Date after, Date before, String query,
String[] mentions, String[] tags, String verb) {
return getEntryCountForFeedId(null, after, before, query, mentions,
tags, verb);
}
public int getEntryCountForFeedId(String feedId, Date after, Date before,
String search, String[] mentions, String[] tags, String verb) {
String token = tokenize(feedId, "getEntryCountForFeedId", after,
before, search, mentions, tags, verb);
Object result = get(token);
if (result == NOT_FOUND) {
result = persistentStorage.getEntryCountForFeedId(feedId, after,
before, search, mentions, tags, verb);
put(token, result);
}
return ((Integer) result).intValue();
}
public String[] getEntryIds(int start, int length, Date after, Date before,
String search, String[] mentions, String[] tags, String verb) {
String token = tokenize("getEntryIds", start, length, after, before,
search, mentions, tags, verb);
Object result = get(token);
if (result == NOT_FOUND) {
result = persistentStorage.getEntryIds(start, length, after,
before, search, mentions, tags, verb);
put(token, result);
}
return (String[]) result;
}
public long[] getEntryIdsForFeedId(String feedId, int start, int length,
Date after, Date before, String search, String[] mentions,
String[] tags, String verb) {
String token = tokenize(feedId, "getEntryIdsForFeedId", start, length,
after, before, search, mentions, tags, verb);
Object result = get(token);
if (result == NOT_FOUND) {
result = persistentStorage.getEntryIdsForFeedId(feedId, start,
length, after, before, search, mentions, tags, verb);
put(token, result);
}
return (long[]) result;
}
public String readFeed(String feedId) throws FileNotFoundException,
IOException {
String token = tokenize(feedId, "readFeed");
Object result = get(token);
if (result == NOT_FOUND) {
result = persistentStorage.readFeed(feedId);
put(token, result);
}
return (String) result;
}
public void updateFeed(String feedId, Date lastUpdated, String content)
throws IOException {
persistentStorage.updateFeed(feedId, lastUpdated, content);
purge(feedId);
}
public String readEntry(String feedId, long entryId)
throws FileNotFoundException, IOException {
String token = tokenize(feedId, "readEntry", entryId);
Object result = get(token);
if (result == NOT_FOUND) {
result = persistentStorage.readEntry(feedId, entryId);
put(token, result);
}
return (String) result;
}
public void updateEntry(String feedId, long entryId, Date publishDate,
String content) throws IOException {
persistentStorage.updateEntry(feedId, entryId, publishDate, content);
purge(feedId);
}
public void deleteEntry(String feedId, long entryId)
throws FileNotFoundException, IOException {
persistentStorage.deleteEntry(feedId, entryId);
purge(feedId);
}
public String readFeedEntryResourceType(String feedId, long entryId,
String resourceId) throws FileNotFoundException, IOException {
String token = tokenize(feedId, "readFeedEntryResourceType", entryId,
resourceId);
Object result = get(token);
if (result == NOT_FOUND) {
result = persistentStorage.readFeedEntryResourceType(feedId,
entryId, resourceId);
put(token, result);
}
return (String) result;
}
public InputStream readFeedEntryResource(String feedId, long entryId,
String resourceId) throws FileNotFoundException, IOException {
// don't cache binary content
return persistentStorage.readFeedEntryResource(feedId, entryId,
resourceId);
}
public void updateFeedEntryResource(String feedId, long entryId,
String resourceId, String mimeType, Date publishDate, byte[] data)
throws IOException {
persistentStorage.updateFeedEntryResource(feedId, entryId, resourceId,
mimeType, publishDate, data);
}
public void deleteFeedEntryResource(String feedId, long entryId,
String resourceId) throws IOException {
persistentStorage.deleteFeedEntryResource(feedId, entryId, resourceId);
}
}