1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.myfaces.trinidad.resource;
20
21 import java.io.ByteArrayInputStream;
22 import java.io.ByteArrayOutputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.net.URL;
26 import java.net.URLConnection;
27 import java.net.URLStreamHandler;
28 import java.util.concurrent.ConcurrentHashMap;
29 import java.util.concurrent.ConcurrentMap;
30
31 import java.util.concurrent.atomic.AtomicReference;
32
33 import org.apache.myfaces.trinidad.logging.TrinidadLogger;
34 import org.apache.myfaces.trinidad.util.Args;
35 import org.apache.myfaces.trinidad.util.URLUtils;
36
37
38
39
40
41
42 public class CachingResourceLoader extends ResourceLoader
43 {
44
45
46
47
48
49 public CachingResourceLoader(
50 ResourceLoader parent)
51 {
52 super(parent);
53
54 _cache = new ConcurrentHashMap<String, URL>();
55 }
56
57
58
59
60
61
62
63
64
65
66
67
68
69 @Override
70 protected URL findResource(
71 String path
72 ) throws IOException
73 {
74 URL url = _cache.get(path);
75
76 if (url == null)
77 {
78 url = getParent().getResource(path);
79
80 if (url != null)
81 {
82 url = new URL("cache", null, -1, path, new CachingURLStreamHandler(url));
83 _cache.putIfAbsent(path, url);
84 }
85 }
86
87 return url;
88 }
89
90 private final ConcurrentMap<String, URL> _cache;
91
92 @Override
93 public boolean isCachable()
94 {
95 return false;
96 }
97
98
99
100
101
102
103 static private final class CachingURLStreamHandler extends URLStreamHandler
104 {
105 public CachingURLStreamHandler(
106 URL delegate)
107 {
108 _delegate = delegate;
109 _contents = new AtomicReference<CachedContents>();
110 }
111
112
113
114
115
116
117 public void validateContentLength(int newContentLength)
118 {
119 CachedContents contents = _contents.get();
120
121 if ((contents != null) && !contents.validateContentLength(newContentLength))
122 {
123
124
125 _contents.compareAndSet(contents, null);
126 _logResourceSizeChanged(newContentLength, contents);
127
128 }
129 }
130
131 private void _logResourceSizeChanged(int newContentLength, CachedContents contents)
132 {
133 if (_LOG.isFine())
134 {
135 _LOG.fine("RESOURCE_SIZE_CHANGED",
136 new Object[]
137 {
138 newContentLength,
139 contents
140 });
141 }
142 }
143
144 @Override
145 protected URLConnection openConnection(
146 URL url
147 ) throws IOException
148 {
149 return new URLConnectionImpl(url, _delegate.openConnection(), this);
150 }
151
152 protected InputStream getInputStream(URLConnection conn) throws IOException
153 {
154 CachedContents contents = _contents.get();
155
156 if (contents == null || _isStale(contents, _delegate))
157 {
158 contents = _updateContents(conn);
159 assert(contents != null);
160 }
161
162 return contents.toInputStream();
163 }
164
165
166 private boolean _isStale(CachedContents contents, URL url) throws IOException
167 {
168 Args.notNull(contents, "contents");
169 Args.notNull(url, "url");
170
171 long lastModified = URLUtils.getLastModified(_delegate);
172 return contents.isStale(lastModified);
173 }
174
175 private CachedContents _updateContents(URLConnection conn) throws IOException
176 {
177 CachedContents newContents = _createContents(conn);
178 assert(newContents != null);
179
180
181
182
183
184 _contents.set(newContents);
185
186 return newContents;
187 }
188
189 private CachedContents _createContents(URLConnection conn) throws IOException
190 {
191
192
193
194
195
196
197 long lastModified = URLUtils.getLastModified(conn);
198 byte[] data = _readBytes(conn);
199 int contentLength = conn.getContentLength();
200
201 return new CachedContents(this._delegate, data, lastModified, contentLength);
202 }
203
204 @SuppressWarnings("oracle.jdeveloper.java.nested-assignment")
205 private byte[] _readBytes(URLConnection conn) throws IOException
206 {
207 InputStream in = conn.getInputStream();
208 ByteArrayOutputStream out = new ByteArrayOutputStream();
209 try
210 {
211 byte[] buffer = new byte[2048];
212 int length;
213 while ((length = (in.read(buffer))) >= 0)
214 {
215 out.write(buffer, 0, length);
216 }
217 }
218 finally
219 {
220 in.close();
221 }
222
223 return out.toByteArray();
224 }
225
226 private final URL _delegate;
227 private final AtomicReference<CachedContents> _contents;
228 }
229
230
231
232
233
234 static private final class CachedContents
235 {
236 public CachedContents(
237 URL resourceURL,
238 byte[] data,
239 long lastModified,
240 int contentLength
241 )
242 {
243 Args.notNull(data, "data");
244 Args.notNull(resourceURL, "resourceURL");
245 _ensureValidSize(resourceURL, data, contentLength);
246
247 this._url = resourceURL;
248 this._data = data;
249 _lastModified = lastModified;
250 _contentLength = contentLength;
251 }
252
253 public InputStream toInputStream()
254 {
255 return new ByteArrayInputStream(_data);
256 }
257
258
259
260
261
262
263
264
265 public boolean isStale(long lastModified)
266 {
267 return (lastModified > _lastModified);
268 }
269
270
271
272
273
274
275
276 public boolean validateContentLength(int newContentLength)
277 {
278 return _isValidSize(_data, newContentLength);
279 }
280
281
282
283
284
285
286
287 @Override
288 public String toString()
289 {
290 String urlString = _url.toString();
291 String sizeString = Integer.toString(_data.length);
292 int builderLength = urlString.length() + sizeString.length() + 13;
293
294 StringBuilder builder = new StringBuilder(builderLength);
295 builder.append("[url=");
296 builder.append(urlString);
297 builder.append(", size=");
298 builder.append(sizeString);
299 builder.append("]");
300
301 return builder.toString();
302 }
303
304 private void _ensureValidSize(
305 URL resourceURL,
306 byte[] data,
307 int contentLength
308 ) throws IllegalStateException
309 {
310 assert(data != null);
311
312 if (!_isValidSize(data, contentLength))
313 {
314 String messageKey = "INVALID_RESOURCE_SIZE";
315 String message = _LOG.getMessage(messageKey,
316 new Object[]
317 {
318 resourceURL.toString(),
319 data.length,
320 contentLength
321 });
322
323 _LOG.severe(message);
324
325
326
327
328 throw new IllegalStateException(messageKey);
329 }
330 }
331
332 private boolean _isValidSize(byte[] data, int contentLength)
333 {
334 assert(data != null);
335 return ((contentLength < 0) || (contentLength == data.length));
336 }
337
338 private final URL _url;
339 private final byte[] _data;
340 private final long _lastModified;
341 private final int _contentLength;
342 }
343
344
345
346
347 static private class URLConnectionImpl extends URLConnection
348 {
349
350
351
352
353
354
355 public URLConnectionImpl(
356 URL url,
357 URLConnection conn,
358 CachingURLStreamHandler handler)
359 {
360 super(url);
361 _conn = conn;
362 _handler = handler;
363 }
364
365 @Override
366 public void connect() throws IOException
367 {
368
369 }
370
371 @Override
372 public String getContentType()
373 {
374 return _conn.getContentType();
375 }
376
377 @Override
378 public int getContentLength()
379 {
380 int contentLength = _conn.getContentLength();
381 _handler.validateContentLength(contentLength);
382
383 return contentLength;
384 }
385
386 @Override
387 public long getLastModified()
388 {
389 try
390 {
391 return URLUtils.getLastModified(_conn);
392 }
393 catch (IOException exception)
394 {
395 return -1;
396 }
397 }
398
399 @Override
400 public String getHeaderField(
401 String name)
402 {
403 return _conn.getHeaderField(name);
404 }
405
406 @Override
407 public InputStream getInputStream() throws IOException
408 {
409 return _handler.getInputStream(_conn);
410 }
411
412 private final URLConnection _conn;
413 private final CachingURLStreamHandler _handler;
414 }
415
416 static private final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(CachingResourceLoader.class);
417 }