5
5
* The ASF licenses this file to You under the Apache License, Version 2.0
6
6
* (the "License"); you may not use this file except in compliance with
7
7
* the License. You may obtain a copy of the License at
9
9
* http://www.apache.org/licenses/LICENSE-2.0
11
11
* Unless required by applicable law or agreed to in writing, software
12
12
* distributed under the License is distributed on an "AS IS" BASIS,
13
13
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
15
* limitations under the License.
18
/* $Id: ImageCache.java 606580 2007-12-23 17:45:02Z jeremias $ */
18
/* $Id: ImageCache.java 816640 2009-09-18 14:14:55Z maxberger $ */
20
20
package org.apache.xmlgraphics.image.loader.cache;
22
22
import java.io.FileNotFoundException;
23
23
import java.io.IOException;
24
24
import java.util.Collections;
25
import java.util.HashSet;
26
import java.util.Iterator;
25
28
import java.util.Set;
27
30
import javax.xml.transform.Source;
45
48
* Don't use one ImageCache instance in the context of multiple base URIs because relative URIs
46
49
* would not work correctly anymore.
51
* By default, the URIs of inaccessible images are remembered but these entries are discarded
52
* after 60 seconds (which causes a retry next time the same URI is requested). This allows
53
* to counteract performance loss when accessing invalid or temporarily unavailable images
54
* over slow connections.
48
56
public class ImageCache {
51
59
protected static Log log = LogFactory.getLog(ImageCache.class);
53
private Set invalidURIs = Collections.synchronizedSet(new java.util.HashSet());
61
//Handling of invalid URIs
62
private Map invalidURIs = Collections.synchronizedMap(new java.util.HashMap());
63
private ExpirationPolicy invalidURIExpirationPolicy;
55
66
private SoftMapCache imageInfos = new SoftMapCache(true);
56
67
private SoftMapCache images = new SoftMapCache(true);
58
69
private ImageCacheListener cacheListener;
70
private TimeStampProvider timeStampProvider;
71
private long lastHouseKeeping;
74
* Default constructor with default settings.
77
this(new TimeStampProvider(), new DefaultExpirationPolicy());
81
* Constructor for customized behaviour and testing.
82
* @param timeStampProvider the time stamp provider to use
83
* @param invalidURIExpirationPolicy the expiration policy for invalid URIs
85
public ImageCache(TimeStampProvider timeStampProvider,
86
ExpirationPolicy invalidURIExpirationPolicy) {
87
this.timeStampProvider = timeStampProvider;
88
this.invalidURIExpirationPolicy = invalidURIExpirationPolicy;
89
this.lastHouseKeeping = this.timeStampProvider.getTimeStamp();
61
93
* Sets an ImageCacheListener instance so the events in the image cache can be observed.
112
144
* Indicates whether a URI has previously been identified as an invalid URI.
113
145
* @param uri the image's URI
114
146
* @return true if the URI is invalid
116
148
public boolean isInvalidURI(String uri) {
117
if (invalidURIs.contains(uri)) {
149
boolean expired = removeInvalidURIIfExpired(uri);
118
153
if (cacheListener != null) {
119
154
cacheListener.invalidHit(uri);
160
private boolean removeInvalidURIIfExpired(String uri) {
161
Long timestamp = (Long) invalidURIs.get(uri);
162
boolean expired = (timestamp == null)
163
|| this.invalidURIExpirationPolicy.isExpired(
164
this.timeStampProvider, timestamp.longValue());
166
this.invalidURIs.remove(uri);
127
172
* Returns an ImageInfo instance from the cache or null if none is found.
128
173
* @param uri the image's URI
150
195
//An already existing ImageInfo is replaced.
151
196
imageInfos.put(info.getOriginalURI(), info);
199
private static final long ONE_HOUR = 60 * 60 * 1000;
155
202
* Registers a URI as invalid so getImageInfo can indicate that quickly with no I/O access.
156
203
* @param uri the URI of the invalid image
158
private void registerInvalidURI(String uri) {
159
synchronized (invalidURIs) {
160
// cap size of invalid list
161
if (invalidURIs.size() > 100) {
164
invalidURIs.add(uri);
205
void registerInvalidURI(String uri) {
206
invalidURIs.put(uri, new Long(timeStampProvider.getTimeStamp()));
208
considerHouseKeeping();
169
212
* Returns an image from the cache or null if it wasn't found.
170
213
* @param info the ImageInfo instance representing the image
174
217
public Image getImage(ImageInfo info, ImageFlavor flavor) {
175
218
return getImage(info.getOriginalURI(), flavor);
179
222
* Returns an image from the cache or null if it wasn't found.
180
223
* @param uri the image's URI
225
268
doHouseKeeping();
271
private void considerHouseKeeping() {
272
long ts = timeStampProvider.getTimeStamp();
273
if (this.lastHouseKeeping + ONE_HOUR > ts) {
274
//Housekeeping is only triggered through registration of an invalid URI at the moment.
275
//Depending on the environment this could be triggered next to never.
276
//Doing this check for every image access could be relatively costly.
277
//The only alternative is a cleanup thread which is rather heavy-weight.
278
this.lastHouseKeeping = ts;
229
284
* Triggers some house-keeping, i.e. removes stale entries.
231
286
public void doHouseKeeping() {
232
287
imageInfos.doHouseKeeping();
233
288
images.doHouseKeeping();
289
doInvalidURIHouseKeeping();
292
private void doInvalidURIHouseKeeping() {
293
final Set currentEntries = new HashSet(this.invalidURIs.keySet());
294
final Iterator iter = currentEntries.iterator();
295
while (iter.hasNext()) {
296
final String key = (String) iter.next();
297
removeInvalidURIIfExpired(key);