View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.http.impl.client.cache.ehcache;
28  
29  import java.io.ByteArrayInputStream;
30  import java.io.ByteArrayOutputStream;
31  import java.io.IOException;
32  
33  import net.sf.ehcache.Ehcache;
34  import net.sf.ehcache.Element;
35  
36  import org.apache.http.client.cache.HttpCacheEntry;
37  import org.apache.http.client.cache.HttpCacheEntrySerializer;
38  import org.apache.http.client.cache.HttpCacheStorage;
39  import org.apache.http.client.cache.HttpCacheUpdateCallback;
40  import org.apache.http.client.cache.HttpCacheUpdateException;
41  import org.apache.http.impl.client.cache.CacheConfig;
42  import org.apache.http.impl.client.cache.DefaultHttpCacheEntrySerializer;
43  
44  /**
45   * <p>This class is a storage backend for cache entries that uses the
46   * popular <a href="http://ehcache.org">Ehcache</a> cache implementation.
47   * In particular, this backend allows for spillover to disk, where the
48   * cache can be effectively larger than memory, and cached responses are
49   * paged into and out of memory from disk as needed.</p>
50   *
51   * <p><b>N.B.</b> Since the Ehcache is configured ahead of time with a
52   * maximum number of cache entries, this effectively ignores the
53   * {@link CacheConfig#setMaxCacheEntries(int) maximum cache entries}
54   * specified by a provided {@link CacheConfig}.</p>
55   *
56   * <p>Please refer to the <a href="http://ehcache.org/documentation/index.html">
57   * Ehcache documentation</a> for details on how to configure the Ehcache
58   * itself.</p>
59   * @since 4.1
60   */
61  public class EhcacheHttpCacheStorage implements HttpCacheStorage {
62  
63      private final Ehcache cache;
64      private final HttpCacheEntrySerializer serializer;
65      private final int maxUpdateRetries;
66  
67      /**
68       * Constructs a storage backend using the provided Ehcache
69       * with default configuration options.
70       * @param cache where to store cached origin responses
71       */
72      public EhcacheHttpCacheStorage(final Ehcache cache) {
73          this(cache, CacheConfig.DEFAULT, new DefaultHttpCacheEntrySerializer());
74      }
75  
76      /**
77       * Constructs a storage backend using the provided Ehcache
78       * with the given configuration options.
79       * @param cache where to store cached origin responses
80       * @param config cache storage configuration options - note that
81       *   the setting for max object size <b>will be ignored</b> and
82       *   should be configured in the Ehcache instead.
83       */
84      public EhcacheHttpCacheStorage(final Ehcache cache, final CacheConfig config){
85          this(cache, config, new DefaultHttpCacheEntrySerializer());
86      }
87  
88      /**
89       * Constructs a storage backend using the provided Ehcache
90       * with the given configuration options, but using an alternative
91       * cache entry serialization strategy.
92       * @param cache where to store cached origin responses
93       * @param config cache storage configuration options - note that
94       *   the setting for max object size <b>will be ignored</b> and
95       *   should be configured in the Ehcache instead.
96       * @param serializer alternative serialization mechanism
97       */
98      public EhcacheHttpCacheStorage(final Ehcache cache, final CacheConfig config, final HttpCacheEntrySerializer serializer){
99          this.cache = cache;
100         this.maxUpdateRetries = config.getMaxUpdateRetries();
101         this.serializer = serializer;
102     }
103 
104     @Override
105     public synchronized void putEntry(final String key, final HttpCacheEntry entry) throws IOException {
106         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
107         serializer.writeTo(entry, bos);
108         cache.put(new Element(key, bos.toByteArray()));
109     }
110 
111     @Override
112     public synchronized HttpCacheEntry getEntry(final String key) throws IOException {
113         final Element e = cache.get(key);
114         if(e == null){
115             return null;
116         }
117 
118         final byte[] data = (byte[])e.getObjectValue();
119         return serializer.readFrom(new ByteArrayInputStream(data));
120     }
121 
122     @Override
123     public synchronized void removeEntry(final String key) {
124         cache.remove(key);
125     }
126 
127     @Override
128     public synchronized void updateEntry(final String key, final HttpCacheUpdateCallback callback)
129             throws IOException, HttpCacheUpdateException {
130         int numRetries = 0;
131         do{
132             final Element oldElement = cache.get(key);
133 
134             HttpCacheEntry existingEntry = null;
135             if(oldElement != null){
136                 final byte[] data = (byte[])oldElement.getObjectValue();
137                 existingEntry = serializer.readFrom(new ByteArrayInputStream(data));
138             }
139 
140             final HttpCacheEntry updatedEntry = callback.update(existingEntry);
141 
142             if (existingEntry == null) {
143                 putEntry(key, updatedEntry);
144                 return;
145             } else {
146                 // Attempt to do a CAS replace, if we fail then retry
147                 // While this operation should work fine within this instance, multiple instances
148                 //  could trample each others' data
149                 final ByteArrayOutputStream bos = new ByteArrayOutputStream();
150                 serializer.writeTo(updatedEntry, bos);
151                 final Element newElement = new Element(key, bos.toByteArray());
152                 if (cache.replace(oldElement, newElement)) {
153                     return;
154                 }else{
155                     numRetries++;
156                 }
157             }
158         }while(numRetries <= maxUpdateRetries);
159         throw new HttpCacheUpdateException("Failed to update");
160     }
161 }