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    }