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 }