package org.jboss.resteasy.plugins.cache.server;
import org.infinispan.Cache;
import org.jboss.resteasy.specimpl.MultivaluedTreeMap;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.NewCookie;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* An HTTP cache that behaves somewhat the same way as a proxy (like Squid)
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class InfinispanCache implements ServerCache
{
public static class CacheEntry implements Entry, Serializable
{
private static final long serialVersionUID = 2848638331930090578L;
private byte[] cached;
private int expires;
private long timestamp = System.currentTimeMillis();
private String etag;
private transient MultivaluedMap<String, Object> headers;
private transient MediaType mediaType;
private transient MultivaluedMap<String, String> varyHeaders;
private CacheEntry(MultivaluedMap<String, Object> headers, byte[] cached, int expires, String etag, MediaType mediaType, MultivaluedMap<String, String> varyHeaders)
{
this.cached = cached;
this.expires = expires;
this.headers = headers;
this.etag = etag;
this.mediaType = mediaType;
this.varyHeaders = varyHeaders;
}
public int getExpirationInSeconds()
{
return expires - (int) ((System.currentTimeMillis() - timestamp) / 1000);
}
public boolean isExpired()
{
return System.currentTimeMillis() - timestamp >= expires * 1000L;
}
public String getEtag()
{
return etag;
}
public MultivaluedMap<String, Object> getHeaders()
{
return headers;
}
public MultivaluedMap<String, String> getVaryHeaders()
{
return varyHeaders;
}
public byte[] getCached()
{
return cached;
}
public MediaType getMediaType()
{
return mediaType;
}
private void writeObject(ObjectOutputStream stream) throws IOException
{
stream.defaultWriteObject();
stream.writeObject(stringifyHeaders(headers));
stream.writeObject(stringifyHeaders(varyHeaders));
stream.writeUTF(mediaType.toString());
}
@SuppressWarnings("unchecked")
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException
{
stream.defaultReadObject();
headers = unstringifyHeaders(MultivaluedMap.class.cast(stream.readObject()));
varyHeaders = unstringifyHeaders(MultivaluedMap.class.cast(stream.readObject()));
mediaType = MediaType.valueOf(stream.readUTF());
}
}
@SuppressWarnings("rawtypes")
protected Cache cache;
@SuppressWarnings("rawtypes")
public InfinispanCache(Cache cache)
{
this.cache = cache;
}
public Entry get(String uri, MediaType accept, MultivaluedMap<String, String> headers)
{
@SuppressWarnings("unchecked")
Set<String> entries = (Set<String>)cache.get(uri);
if (entries == null) return null;
for (String entry : entries)
{
CacheEntry cacheEntry = (CacheEntry)cache.get(entry);
if (cacheEntry == null) continue;
if (accept.isCompatible(cacheEntry.getMediaType()) && !ServerCache.mayVary(cacheEntry, headers))
{
return cacheEntry;
}
}
return null;
}
@SuppressWarnings("unchecked")
public Entry add(String uri, MediaType mediaType, CacheControl cc, MultivaluedMap<String, Object> headers, byte[] entity, String etag, MultivaluedMap<String, String> varyHeaders)
{
// there's a race condition here with a concurrent get() method above. Too bad JBoss Cache doesn't have a way to create
// a node before hand then insert it
CacheEntry cacheEntry = new CacheEntry(headers, entity, cc.getMaxAge(), etag, mediaType, varyHeaders);
StringBuffer varyHeadersString = new StringBuffer();
varyHeaders.forEach((name, values) -> values.forEach(value -> varyHeadersString.append(name).append(value)));
String entryName = uri + " " + mediaType.toString() + " " + varyHeadersString.toString();
Set<String> entries = (Set<String>)cache.get(uri);
Set<String> newEntries = new HashSet<String>();
newEntries.add(entryName);
if (entries != null)
{
newEntries.addAll(entries);
}
cache.put(uri, newEntries);
cache.put(entryName, cacheEntry, cc.getMaxAge(), TimeUnit.SECONDS);
return cacheEntry;
}
public void remove(String uri)
{
@SuppressWarnings("unchecked")
Set<String> entries = (Set<String>)cache.remove(uri);
if (entries == null) return;
for (String entry : entries)
{
cache.remove(entry);
}
}
public void clear()
{
cache.clear();
}
protected static MultivaluedMap<String, ?> stringifyHeaders(MultivaluedMap<String, ?> headers)
{
MultivaluedMap<String, Object> holders = new MultivaluedTreeMap<String, Object>();
for (Iterator<String> it1 = headers.keySet().iterator(); it1.hasNext(); )
{
String key = it1.next();
List<Object> outList = new ArrayList<Object>();
holders.put(key, outList);
List<?> list = headers.get(key);
for (Iterator<?> it2 = list.iterator(); it2.hasNext(); )
{
Object o = it2.next();
if (o instanceof CacheControl)
{
outList.add(new HeaderHolder(HeaderHolder.Type.CACHE_CONTROL, CacheControl.class.cast(o).toString()));
}
else if (o instanceof Cookie)
{
outList.add(new HeaderHolder(HeaderHolder.Type.COOKIE, Cookie.class.cast(o).toString()));
}
else if (o instanceof EntityTag)
{
outList.add(new HeaderHolder(HeaderHolder.Type.ENTITY_TAG, EntityTag.class.cast(o).toString()));
}
else if (o instanceof NewCookie)
{
outList.add(new HeaderHolder(HeaderHolder.Type.NEW_COOKIE, NewCookie.class.cast(o).toString()));
}
else
{
outList.add(new HeaderHolder(HeaderHolder.Type.OTHER, o.toString()));
}
}
}
return holders;
}
protected static MultivaluedMap<String, Object> unstringifyHeaders(MultivaluedMap<String, Object> headers)
{
MultivaluedMap<String, Object> holders = new MultivaluedTreeMap<String, Object>();
for (Iterator<String> it1 = headers.keySet().iterator(); it1.hasNext(); )
{
String key = it1.next();
List<Object> outList = new ArrayList<Object>();
holders.put(key, outList);
List<Object> list = headers.get(key);
for (Iterator<Object> it2 = list.iterator(); it2.hasNext(); )
{
HeaderHolder holder = HeaderHolder.class.cast(it2.next());
if (HeaderHolder.Type.CACHE_CONTROL.equals(holder.getType()))
{
outList.add(CacheControl.valueOf(holder.getValue()));
}
else if (HeaderHolder.Type.COOKIE.equals(holder.getType()))
{
outList.add(Cookie.valueOf(holder.getValue()));
}
else if (HeaderHolder.Type.ENTITY_TAG.equals(holder.getType()))
{
outList.add(EntityTag.valueOf(holder.getValue()));
}
else if (HeaderHolder.Type.NEW_COOKIE.equals(holder.getType()))
{
outList.add(NewCookie.valueOf(holder.getValue()));
}
else
{
outList.add(holder.getValue());
}
}
}
return holders;
}
}