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.callback;
020
021 import java.util.List;
022 import java.util.logging.Logger;
023
024 import org.fusesource.restygwt.client.Method;
025 import org.fusesource.restygwt.client.cache.CacheKey;
026 import org.fusesource.restygwt.client.cache.ComplexCacheKey;
027 import org.fusesource.restygwt.client.cache.Domain;
028 import org.fusesource.restygwt.client.cache.QueueableCacheStorage;
029
030 import com.google.gwt.core.client.GWT;
031 import com.google.gwt.http.client.Request;
032 import com.google.gwt.http.client.RequestBuilder;
033 import com.google.gwt.http.client.RequestCallback;
034 import com.google.gwt.http.client.Response;
035 import com.google.gwt.json.client.JSONArray;
036 import com.google.gwt.json.client.JSONParser;
037 import com.google.gwt.json.client.JSONValue;
038 import com.google.gwt.logging.client.LogConfiguration;
039
040 public class CachingCallbackFilter implements CallbackFilter {
041
042 protected final QueueableCacheStorage cache;
043
044 public CachingCallbackFilter(QueueableCacheStorage cache) {
045 this.cache = cache;
046 }
047
048 /**
049 * the real filter method, called independent of the response code
050 *
051 * TODO method.getResponse() is not equal to response. unfortunately
052 */
053 @Override
054 public RequestCallback filter(final Method method, final Response response,
055 RequestCallback callback) {
056 final int code = response.getStatusCode();
057
058 final CacheKey ck = cacheKey(method.builder);
059 final List<RequestCallback> removedCallbacks = cache.removeCallbacks(ck);
060
061 if (removedCallbacks != null){
062 //TODO ????? && 1 < removedCallbacks.size()) {
063 //TODO ????? remove the first callback from list, as this is called explicitly
064 //TODO ??????removedCallbacks.remove(0);
065 // fetch the builders callback and wrap it with a new one, calling all others too
066 final RequestCallback originalCallback = callback;
067
068 callback = new RequestCallback() {
069 @Override
070 public void onResponseReceived(Request request, Response response) {
071 // call the original callback
072 if (GWT.isClient() && LogConfiguration.loggingIsEnabled()) {
073 Logger.getLogger(CachingCallbackFilter.class.getName())
074 .finer("call original callback for " + ck);
075 }
076 originalCallback.onResponseReceived(request, response);
077
078 if (GWT.isClient() && LogConfiguration.loggingIsEnabled()) {
079 Logger.getLogger(CachingCallbackFilter.class.getName())
080 .finer("call "+ removedCallbacks.size()
081 + " more queued callbacks for " + ck);
082 }
083
084 // and all the others, found in cache
085 for (RequestCallback cb : removedCallbacks) {
086 cb.onResponseReceived(request, response);
087 }
088 }
089
090 @Override
091 public void onError(Request request, Throwable exception) {
092 if (LogConfiguration.loggingIsEnabled()) {
093 Logger.getLogger(CachingCallbackFilter.class.getName())
094 .severe("cannot call " + (removedCallbacks.size()+1)
095 + " callbacks for " + ck + " due to error: "
096 + exception.getMessage());
097 }
098 // call the original callback
099 if (LogConfiguration.loggingIsEnabled()) {
100 Logger.getLogger(CachingCallbackFilter.class.getName())
101 .finer("call original callback for " + ck);
102 }
103
104 originalCallback.onError(request, exception);
105
106 if (LogConfiguration.loggingIsEnabled()) {
107 Logger.getLogger(CachingCallbackFilter.class.getName())
108 .finer("call "+ removedCallbacks.size()
109 + " more queued callbacks for " + ck);
110 }
111
112 // and all the others, found in cache
113 for (RequestCallback cb : removedCallbacks) {
114 cb.onError(request, exception);
115 }
116 }
117 };
118 } else {
119 if (GWT.isClient() && LogConfiguration.loggingIsEnabled()) {
120 Logger.getLogger(CachingCallbackFilter.class.getName()).finer("removed one or no " +
121 "callback for cachekey " + ck);
122 }
123 }
124
125 if (code < Response.SC_MULTIPLE_CHOICES // code < 300
126 && code >= Response.SC_OK) { // code >= 200
127 cacheResult(method, response);
128 return callback;
129 }
130
131 if (GWT.isClient() && LogConfiguration.loggingIsEnabled()) {
132 Logger.getLogger(CachingCallbackFilter.class.getName())
133 .info("cannot cache due to invalid response code: " + code);
134 }
135 return callback;
136 }
137
138 protected CacheKey cacheKey(final RequestBuilder builder) {
139 return new ComplexCacheKey(builder);
140 }
141
142 protected void cacheResult(final Method method, final Response response) {
143 CacheKey cacheKey = cacheKey(method.builder);
144 if (GWT.isClient() && LogConfiguration.loggingIsEnabled()) {
145 Logger.getLogger(CachingCallbackFilter.class.getName()).finer("cache to " + cacheKey
146 + ": " + response);
147 }
148 cache.putResult(cacheKey, response, getCacheDomains(method));
149 }
150
151 /**
152 * when using the {@link Domain} annotation on services, we are able to group responses
153 * of a service to invalidate them later on more fine grained. this method resolves a
154 * possible ``domain`` to allow grouping.
155 *
156 * @return
157 */
158 protected String[] getCacheDomains(final Method method) {
159 if (null == method.getData().get(Domain.CACHE_DOMAIN_KEY)) return null;
160
161 final JSONValue jsonValue = JSONParser.parseStrict(method.getData()
162 .get(Domain.CACHE_DOMAIN_KEY));
163 if (null == jsonValue) return null;
164
165 JSONArray jsonArray = jsonValue.isArray();
166 final String[] dd = new String[jsonArray.size()];
167
168 if (null != jsonArray) {
169 for (int i = 0; i < jsonArray.size(); ++i) {
170 dd[i] = jsonArray.get(i).isString().stringValue();
171 }
172
173 return dd;
174 }
175 return null;
176 }
177 }