001 /**
002 * Copyright (C) 2009-2011 the original author or authors.
003 * See the notice.md file distributed with this work for additional
004 * information regarding copyright ownership.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 package org.fusesource.restygwt.client.cache;
020
021 import java.util.Arrays;
022 import java.util.HashMap;
023 import java.util.List;
024 import java.util.Map;
025 import java.util.logging.Logger;
026
027 import com.google.gwt.core.client.GWT;
028 import com.google.gwt.http.client.Header;
029 import com.google.gwt.http.client.RequestCallback;
030 import com.google.gwt.http.client.Response;
031 import com.google.gwt.logging.client.LogConfiguration;
032
033 /**
034 * this implementation stores Response objects until they are removed or purged. when retrieved from
035 * the cache the Response will have an extra header field "X-Resty-Cache". this allows CallbackFilter to
036 * determine the action on whether the Response came from the cache or just came over the wire.
037 *
038 * @author kristian
039 *
040 */
041 public class DefaultQueueableCacheStorage implements QueueableCacheStorage {
042
043 static class ResponseWrapper extends Response {
044
045 // keep it package private for testing
046 Response response;
047
048 public boolean equals(Object obj) {
049 return response.equals(obj);
050 }
051
052 public String getHeader(String header) {
053 if(RESTY_CACHE_HEADER.equals(header)){
054 return "true";
055 }
056 return response.getHeader(header);
057 }
058
059 public Header[] getHeaders() {
060 List<Header> headers = Arrays.asList(response.getHeaders());
061 headers.add(new Header() {
062
063 @Override
064 public String getValue() {
065 return "true";
066 }
067
068 @Override
069 public String getName() {
070 return RESTY_CACHE_HEADER;
071 }
072 });
073 return (Header[]) headers.toArray();
074 }
075
076 public String getHeadersAsString() {
077 return response.getHeadersAsString() + RESTY_CACHE_HEADER + "=true\r\n";
078 }
079
080 public int getStatusCode() {
081 return response.getStatusCode();
082 }
083
084 public String getStatusText() {
085 return response.getStatusText();
086 }
087
088 public String getText() {
089 return response.getText();
090 }
091
092 public int hashCode() {
093 return response.hashCode();
094 }
095
096 public String toString() {
097 return response.toString();
098 }
099
100 ResponseWrapper(Response resp){
101 this.response = resp;
102 }
103 }
104
105 private static final String DEFAULT_SCOPE = "";
106
107 /**
108 * key <> value hashmap for holding cache values. nothing special here.
109 *
110 * invalidated values will be dropped by timer
111 */
112 protected final Map<String, HashMap<CacheKey, Response>> cache =
113 new HashMap<String, HashMap<CacheKey, Response>>();
114
115 private final Map<CacheKey, List<RequestCallback>> pendingCallbacks =
116 new HashMap<CacheKey, List<RequestCallback>>();
117
118 public Response getResultOrReturnNull(CacheKey key) {
119 return getResultOrReturnNull(key, DEFAULT_SCOPE);
120 }
121
122 public Response getResultOrReturnNull(final CacheKey key, final String scope) {
123 final HashMap<CacheKey, Response> scoped = cache.get(scope);
124 if (null != scoped) {
125 Response result = scoped.get(key);
126 if(result != null){
127 return new ResponseWrapper(result);
128 }
129 }
130
131 return null;
132 }
133
134 @Override
135 public void putResult(final CacheKey key, final Response response) {
136 putResult(key, response, DEFAULT_SCOPE);
137 }
138
139
140 protected void putResult(final CacheKey key, final Response response, final String scope) {
141 HashMap<CacheKey, Response> scoped = cache.get(scope);
142
143 if (null == scoped) {
144 cache.put(scope, new HashMap<CacheKey, Response>());
145 scoped = cache.get(scope);
146 }
147
148 scoped.put(key, response);
149 }
150
151 @Override
152 public void putResult(CacheKey key, Response response, String... scopes) {
153 if (null == scopes) {
154 putResult(key, response);
155 return;
156 }
157
158 // TODO mark multi-scoped values as one invalidation group
159 // TODO remove redundant storage
160 for (String scope : scopes) {
161 putResult(key, response, scope);
162 }
163 }
164
165
166 @Override
167 public boolean hasCallback(final CacheKey k) {
168 return pendingCallbacks.containsKey(k);
169 }
170
171 @Override
172 public void addCallback(final CacheKey k, final RequestCallback rc) {
173 //init value of key if not there...
174 if (!pendingCallbacks.containsKey(k)) {
175 pendingCallbacks.put(k, new java.util.LinkedList<RequestCallback>());
176 }
177
178 // just add callbacks which are not already there
179 if(!pendingCallbacks.get(k).contains(rc)){
180 pendingCallbacks.get(k).add(rc);
181 }
182 }
183
184 @Override
185 public List<RequestCallback> removeCallbacks(final CacheKey k) {
186 return pendingCallbacks.remove(k);
187 }
188
189 @Override
190 public void purge() {
191 if (GWT.isClient() && LogConfiguration.loggingIsEnabled()) {
192 Logger.getLogger(DefaultQueueableCacheStorage.class.getName()).finer("remove "
193 + cache.size() + " elements from cache.");
194 }
195 cache.clear();
196 }
197
198 @Override
199 public void purge(final String scope) {
200 HashMap<CacheKey, Response> scoped = cache.get(scope);
201
202 // TODO handle timers in scoping too
203 if(null != scoped) scoped.clear();
204 }
205
206 @Override
207 public void remove(CacheKey key) {
208 doRemove(key, DEFAULT_SCOPE);
209 }
210
211 @Override
212 public void remove(CacheKey key, String... scopes) {
213 if(scopes != null){
214 for(String scope: scopes){
215 doRemove(key, scope);
216 }
217 }
218 }
219
220 private void doRemove(CacheKey key, String scope){
221 if (GWT.isClient() && LogConfiguration.loggingIsEnabled()) {
222 Logger.getLogger(DefaultQueueableCacheStorage.class.getName())
223 .finer(
224 "removing cache-key " + key + " from scope \""
225 + scope + "\"");
226 }
227
228 HashMap<CacheKey, Response> scoped = cache.get(scope);
229 if(null != scoped) scoped.remove(key);
230 }
231 }