source: trunk/abcl/src/org/armedbear/lisp/ZipCache.java @ 12517

Last change on this file since 12517 was 12504, checked in by Mark Evenson, 15 years ago

Implement HTTP HEAD Last-Modified checking for ZipCache? objects.

Since it seems that no Sun-derived JVM ever invalidates its cache of
URLConnection objects, we have to check for modification "manually".
This implementation is "better than nothing", but not expected to be
especially robust. Most notably, this implementation does not attempt
to use http proxies if they have been specified to the JVM by the
'http.proxy' system property.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 9.6 KB
Line 
1/*
2 * ZipCache.java
3 *
4 * Copyright (C) 2010 Mark Evenson
5 * $Id: ZipCache.java 12504 2010-02-23 14:34:45Z mevenson $
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 *
21 * As a special exception, the copyright holders of this library give you
22 * permission to link this library with independent modules to produce an
23 * executable, regardless of the license terms of these independent
24 * modules, and to copy and distribute the resulting executable under
25 * terms of your choice, provided that you also meet, for each linked
26 * independent module, the terms and conditions of the license of that
27 * module.  An independent module is a module which is not derived from
28 * or based on this library.  If you modify this library, you may extend
29 * this exception to your version of the library, but you are not
30 * obligated to do so.  If you do not wish to do so, delete this
31 * exception statement from your version.
32 */
33package org.armedbear.lisp;
34
35import org.armedbear.lisp.util.HttpHead;
36import static org.armedbear.lisp.Lisp.*;
37
38import java.io.File;
39import java.io.IOException;
40import java.net.JarURLConnection;
41import java.net.MalformedURLException;
42import java.net.URL;
43import java.net.URLConnection;
44import java.text.ParseException;
45import java.text.SimpleDateFormat;
46import java.util.Date;
47import java.util.HashMap;
48import java.util.zip.ZipException;
49import java.util.zip.ZipFile;
50
51/**
52 * A cache for all zip/jar file accesses by URL that uses the last
53 * modified time of the cached resource.
54 *
55 * This implementation is NOT thread safe, although usage without
56 * multiple threads recompiling code that is then re-loaded should be
57 * fine.
58 *
59 * If you run into problems with caching, use
60 * (SYS::DISABLE-ZIP-CACHE).  Once disabled, the caching cannot be
61 * re-enabled.
62 *
63 */ 
64public class ZipCache {
65
66    // To make this thread safe, we should return a proxy for ZipFile
67    // that keeps track of the number of outstanding references handed
68    // out, not allowing ZipFile.close() to succeed until that count
69    // has been reduced to 1 or the finalizer is executing.
70    // Unfortunately the relatively simple strategy of extending
71    // ZipFile via a CachedZipFile does not work because there is not
72    // a null arg constructor for ZipFile.
73    static class Entry {
74        long lastModified;
75        ZipFile file;
76    }
77
78    static boolean cacheEnabled = true;
79
80    private final static Primitive DISABLE_ZIP_CACHE = new disable_zip_cache();
81    final static class disable_zip_cache extends Primitive {
82        disable_zip_cache() {
83            super("disable-zip-cache", PACKAGE_SYS, true, "",
84                  "Disable all caching of ABCL FASLs and ZIPs.");
85        }
86        @Override
87        public LispObject execute() {
88            ZipCache.disable();
89            return T;
90        }
91    }
92
93    static public synchronized void disable() {
94        cacheEnabled = false;
95        zipCache.clear(); 
96    }
97
98    static HashMap<URL, Entry> zipCache = new HashMap<URL, Entry>();
99
100    synchronized public static ZipFile get(LispObject arg) {
101        return get(Pathname.makeURL(arg));
102    }
103
104    static final SimpleDateFormat RFC_1123
105        = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz");
106
107    synchronized public static ZipFile get(final URL url) {
108        if (!cacheEnabled) {
109            if (url.getProtocol().equals("file")) {
110                File f = new File(url.getPath());
111                try { 
112                    return new ZipFile(f);
113                } catch (ZipException e) {
114                    Debug.trace(e); // XXX
115                    return null;
116                } catch (IOException e) {
117                    Debug.trace(e); // XXX
118                    return null;
119                }
120            } else {
121                Entry e = fetchURL(url, false);
122                return e.file;
123            }
124        }               
125
126        Entry entry = zipCache.get(url);
127
128        // Check that the cache entry still accesses a valid ZipFile
129        if (entry != null) {
130            // Simplest way to call private ZipFile.ensureOpen()
131            try {
132                int size = entry.file.size(); 
133            } catch (IllegalStateException e) {
134                zipCache.remove(url);
135                entry = null;
136            }
137        }
138
139        if (entry != null) {
140            if (url.getProtocol().equals("file")) {
141                File f = new File(url.getPath());
142                long current = f.lastModified();
143                if (current > entry.lastModified) {
144                    try {
145                        entry.file = new ZipFile(f);
146                        entry.lastModified = current;
147                    } catch (IOException e) {
148                        Debug.trace(e.toString()); // XXX
149                    }
150                }
151            } else if (url.getProtocol().equals("http")) {
152                // Unfortunately, the Apple JDK under OS X doesn't do
153                // HTTP HEAD requests, instead refetching the entire
154                // resource, and I assume this is the case in all
155                // Sun-derived JVMs.  So, we use a custom HEAD
156                // implementation only looking for Last-Modified
157                // headers, which if we don't find, we give up and
158                // refetch the resource.n
159                String dateString = HttpHead.get(url, "Last-Modified");
160                Date date = null;
161                try {
162                    date = RFC_1123.parse(dateString);
163                    long current = date.getTime();
164                    if (current > entry.lastModified) {
165                        entry = fetchURL(url, false);
166                        zipCache.put(url, entry);
167                    }
168                } catch (ParseException e) {
169                   Debug.trace("Failed to parse HTTP Last-Modified field: " + e);
170                   entry = fetchURL(url, false);
171                   zipCache.put(url, entry);
172                }
173           } else { 
174                entry = fetchURL(url, false);
175                zipCache.put(url, entry);
176            }
177        } else {
178            if (url.getProtocol().equals("file")) {
179                entry = new Entry();
180                File f = new File(url.getPath());
181                entry.lastModified = f.lastModified();
182                try {
183                    entry.file = new ZipFile(f);
184                } catch (ZipException e) {
185                    Debug.trace(e); // XXX
186                    return null;
187                } catch (IOException e) {
188                    Debug.trace(e); // XXX
189                    return null;
190                }
191            } else {
192                entry = fetchURL(url, true);
193            }
194            zipCache.put(url, entry);
195        }
196        return entry.file;
197    }
198     
199    static private Entry fetchURL(URL url, boolean cached) {
200        Entry result = new Entry();
201        URL jarURL = null;
202        try {
203            jarURL = new URL("jar:" + url + "!/");
204        } catch (MalformedURLException e) {
205            Debug.trace(e);
206            Debug.assertTrue(false); // XXX
207        }
208        URLConnection connection;
209        try {
210            connection = jarURL.openConnection();
211        } catch (IOException ex) {
212            Debug.trace("Failed to open "
213                        + "'" + jarURL + "'");
214            return null;
215        }
216        if (!(connection instanceof JarURLConnection)) {
217            // XXX
218            Debug.trace("Could not get a URLConnection from " + jarURL);
219            return null;
220        }
221        JarURLConnection jarURLConnection = (JarURLConnection) connection;
222        jarURLConnection.setUseCaches(cached);
223        try {
224            result.file = jarURLConnection.getJarFile();
225        } catch (IOException e) {
226            Debug.trace(e);
227            Debug.assertTrue(false); // XXX
228        }
229        result.lastModified = jarURLConnection.getLastModified();
230        return result;
231    }
232
233    // ## remove-zip-cache-entry pathname => boolean
234    private static final Primitive REMOVE_ZIP_CACHE_ENTRY = new remove_zip_cache_entry();
235    private static class remove_zip_cache_entry extends Primitive { 
236        remove_zip_cache_entry() {
237            super("remove-zip-cache-entry", PACKAGE_SYS, true, "pathname");
238        }
239        @Override
240        public LispObject execute(LispObject arg) {
241            Pathname p = coerceToPathname(arg);
242            boolean result = ZipCache.remove(p);
243            return result ? T : NIL;
244        }
245    }
246     
247    synchronized public static boolean remove(URL url) {
248        Entry entry = zipCache.get(url);
249        if (entry != null) {
250            try {
251                entry.file.close();
252            } catch (IOException e) {}
253            zipCache.remove(entry);
254            return true;
255        }
256        return false;
257    }
258
259    synchronized public static boolean remove(Pathname p) {
260        URL url = Pathname.makeURL(p);
261        if (url == null) {
262            return false;
263        }
264        return ZipCache.remove(url);
265    }
266
267    synchronized public static boolean remove(File f) {
268        Pathname p = Pathname.makePathname(f);
269        return ZipCache.remove(p);
270    }
271}
Note: See TracBrowser for help on using the repository browser.