/**
* Copyright (C) 2009-2012 the original author or authors.
* See the notice.md file distributed with this work for additional
* information regarding copyright ownership.
*
* 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.fusesource.restygwt.client.cache;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import com.google.gwt.core.client.GWT;
import com.google.gwt.http.client.Header;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.Response;
import com.google.gwt.logging.client.LogConfiguration;
/**
* this implementation stores Response objects until they are removed or purged. when retrieved from
* the cache the Response will have an extra header field "X-Resty-Cache". this allows CallbackFilter to
* determine the action on whether the Response came from the cache or just came over the wire.
*
* @author kristian
*
*/
public class DefaultQueueableCacheStorage implements QueueableCacheStorage {
public static class ResponseWrapper extends Response {
// keep it public for testing
public final Response response;
@Override
public boolean equals(Object obj) {
return response.equals(obj);
}
@Override
public String getHeader(String header) {
if (RESTY_CACHE_HEADER.equals(header)) {
return "true";
}
return response.getHeader(header);
}
@Override
public Header[] getHeaders() {
List<Header> headers = Arrays.asList(response.getHeaders());
headers.add(new Header() {
@Override
public String getValue() {
return "true";
}
@Override
public String getName() {
return RESTY_CACHE_HEADER;
}
});
return (Header[]) headers.toArray();
}
@Override
public String getHeadersAsString() {
return response.getHeadersAsString() + RESTY_CACHE_HEADER
+ "=true\r\n";
}
@Override
public int getStatusCode() {
return response.getStatusCode();
}
@Override
public String getStatusText() {
return response.getStatusText();
}
@Override
public String getText() {
return response.getText();
}
@Override
public int hashCode() {
return response.hashCode();
}
@Override
public String toString() {
return response.toString();
}
ResponseWrapper(Response resp) {
this.response = resp;
}
}
private static final String DEFAULT_SCOPE = "";
/**
* key <> value hashmap for holding cache values. nothing special here.
*
* invalidated values will be dropped by timer
*/
protected final Map<String, HashMap<CacheKey, Response>> cache = new HashMap<String, HashMap<CacheKey, Response>>();
private final Map<CacheKey, List<RequestCallback>> pendingCallbacks = new HashMap<CacheKey, List<RequestCallback>>();
@Override
public Response getResultOrReturnNull(CacheKey key) {
return getResultOrReturnNull(key, DEFAULT_SCOPE);
}
@Override
public Response getResultOrReturnNull(final CacheKey key, final String scope) {
final HashMap<CacheKey, Response> scoped = cache.get(scope);
if (null != scoped) {
Response result = scoped.get(key);
if (result != null) {
return new ResponseWrapper(result);
}
}
return null;
}
@Override
public void putResult(final CacheKey key, final Response response) {
putResult(key, response, DEFAULT_SCOPE);
}
protected void putResult(final CacheKey key, final Response response,
final String scope) {
HashMap<CacheKey, Response> scoped = cache.get(scope);
if (null == scoped) {
cache.put(scope, new HashMap<CacheKey, Response>());
scoped = cache.get(scope);
}
scoped.put(key, response);
}
@Override
public void putResult(CacheKey key, Response response, String... scopes) {
if (null == scopes) {
putResult(key, response);
return;
}
// TODO mark multi-scoped values as one invalidation group
// TODO remove redundant storage
for (String scope : scopes) {
putResult(key, response, scope);
}
}
@Override
public boolean hasCallback(final CacheKey k) {
return pendingCallbacks.containsKey(k);
}
@Override
public void addCallback(final CacheKey k, final RequestCallback rc) {
// init value of key if not there...
if (!pendingCallbacks.containsKey(k)) {
pendingCallbacks
.put(k, new java.util.LinkedList<RequestCallback>());
}
// just add callbacks which are not already there
if (!pendingCallbacks.get(k).contains(rc)) {
pendingCallbacks.get(k).add(rc);
}
}
@Override
public List<RequestCallback> removeCallbacks(final CacheKey k) {
return pendingCallbacks.remove(k);
}
@Override
public void purge() {
if (GWT.isClient() && LogConfiguration.loggingIsEnabled()) {
Logger.getLogger(DefaultQueueableCacheStorage.class.getName())
.finer("remove " + cache.size() + " elements from cache.");
}
cache.clear();
}
@Override
public void purge(final String scope) {
HashMap<CacheKey, Response> scoped = cache.get(scope);
// TODO handle timers in scoping too
if (null != scoped)
scoped.clear();
}
@Override
public void remove(CacheKey key) {
doRemove(key, DEFAULT_SCOPE);
}
@Override
public void remove(CacheKey key, String... scopes) {
if (scopes != null) {
for (String scope : scopes) {
doRemove(key, scope);
}
}
}
private void doRemove(CacheKey key, String scope) {
if (GWT.isClient() && LogConfiguration.loggingIsEnabled()) {
Logger.getLogger(DefaultQueueableCacheStorage.class.getName())
.finer("removing cache-key " + key + " from scope \""
+ scope + "\"");
}
HashMap<CacheKey, Response> scoped = cache.get(scope);
if (null != scoped)
scoped.remove(key);
}
}