source: branches/streams/abcl/src/org/armedbear/lisp/ZipCache.java

Last change on this file was 14627, checked in by Mark Evenson, 11 years ago

(partially) restore CL:LOAD from jar files.

There is apparently a fair amount of "breakage" of cases that used to
load no longer working which seems due to the changes in the semantics
for finding the FASL init loader. The following tests are now broken
but no longer cause the JVM to crash: JAR-PATHNAME.LOAD.HTTP.1,
JAR-PATHNAME.LOAD.HTTP.2, JAR-PATHNAME.LOAD.HTTP.4,
JAR-PATHNAME.LOAD.HTTP.6, JAR-PATHNAME.LOAD.HTTP.7,
and JAR-PATHNAME.LOAD.HTTP.9. Need to follow this up in subsequent work.

Fixed the underlying HttpHead?.get() interface used to determine
whether to used a cache version. The custom HTTP HEAD code that was
working under Java 6 no longer worked on Java 7.

Added to HttpHead?.get() asynchronous java.lang.Throwable on a socket
timeout of 5000 ms.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 11.4 KB
Line 
1/*
2 * ZipCache.java
3 *
4 * Copyright (C) 2010, 2014 Mark Evenson
5 * $Id: ZipCache.java 14627 2014-02-16 21:25:20Z 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.ParsePosition;
45import java.text.SimpleDateFormat;
46import java.util.Date;
47import java.util.HashMap;
48import java.util.Locale;
49import java.util.zip.ZipException;
50import java.util.zip.ZipFile;
51
52/**
53 * A cache for all zip/jar file accesses by URL that uses the last
54 * modified time of the cached resource.
55 *
56 * This implementation is synchronized on accesses via get().
57 * Usage without multiple threads recompiling code that is then
58 * re-loaded should be fine.
59 *
60 * If you run into problems with caching, use
61 * (SYS::DISABLE-ZIP-CACHE).  Once disabled, the caching cannot be
62 * re-enabled.
63 *
64 */ 
65public class ZipCache {
66
67    // To make this thread safe, we should return a proxy for ZipFile
68    // that keeps track of the number of outstanding references handed
69    // out, not allowing ZipFile.close() to succeed until that count
70    // has been reduced to 1 or the finalizer is executing.
71    // Unfortunately the relatively simple strategy of extending
72    // ZipFile via a CachedZipFile does not work because there is not
73    // a null arg constructor for ZipFile.
74    static class Entry {
75        long lastModified;
76        ZipFile file;
77    }
78
79    static boolean cacheEnabled = true;
80
81    private final static Primitive DISABLE_ZIP_CACHE = new disable_zip_cache();
82    final static class disable_zip_cache extends Primitive {
83        disable_zip_cache() {
84            super("disable-zip-cache", PACKAGE_SYS, true, "",
85                  "Disable all caching of ABCL FASLs and ZIPs.");
86        }
87        @Override
88        public LispObject execute() {
89            ZipCache.disable();
90            return T;
91        }
92    }
93
94    static public synchronized void disable() {
95        cacheEnabled = false;
96        zipCache.clear(); 
97    }
98
99    static HashMap<URL, Entry> zipCache = new HashMap<URL, Entry>();
100
101    synchronized public static ZipFile get(Pathname p) {
102        return get(Pathname.makeURL(p));
103    }
104
105    static final SimpleDateFormat ASCTIME
106        = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy", Locale.US);
107    static final SimpleDateFormat RFC_1036
108        = new SimpleDateFormat("EEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US);
109    static final SimpleDateFormat RFC_1123
110        = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
111
112
113    synchronized public static ZipFile get(final URL url) {
114        if (!cacheEnabled) {
115            if (url.getProtocol().equals("file")) {
116                File f = new File(url.getPath());
117                try { 
118                    return new ZipFile(f);
119                } catch (ZipException e) {
120                    error(new FileError("Failed to construct ZipFile"
121                                        + " because " + e,
122                                        Pathname.makePathname(f)));
123                } catch (IOException e) {
124                    error(new FileError("Failed to contruct ZipFile"
125                                        + " because " + e,
126                                        Pathname.makePathname(f)));
127                }
128            } else {
129                Entry e = fetchURL(url, false);
130                return e.file;
131            }
132        }               
133
134        Entry entry = zipCache.get(url);
135
136        // Check that the cache entry still accesses a valid ZipFile
137        if (entry != null) {
138            // Simplest way to call private ZipFile.ensureOpen()
139            try {
140                int size = entry.file.size(); 
141            } catch (IllegalStateException e) {
142                zipCache.remove(url);
143                entry = null;
144            }
145        }
146
147        if (entry != null) {
148            if (url.getProtocol().equals("file")) {
149                File f = new File(url.getPath());
150                long current = f.lastModified();
151                if (current > entry.lastModified) {
152                    try {
153                        entry.file = new ZipFile(f);
154                        entry.lastModified = current;
155                    } catch (IOException e) {
156                        Debug.trace(e.toString()); // XXX
157                    }
158                }
159            } else if (url.getProtocol().equals("http")) {
160                // Unfortunately, the Apple JDK under OS X doesn't do
161                // HTTP HEAD requests, instead refetching the entire
162                // resource, and I assume this is the case in all
163                // Sun-derived JVMs.  So, we use a custom HEAD
164                // implementation only looking for Last-Modified
165                // headers, which if we don't find, we give up and
166                // refetch the resource.
167                String dateString = null;
168                try {
169                  dateString = HttpHead.get(url, "Last-Modified");
170                } catch (IOException ex) {
171                  Debug.trace(ex);
172                }
173                Date date = null;
174                ParsePosition pos = new ParsePosition(0);
175
176                if (dateString != null) {
177                    date = RFC_1123.parse(dateString, pos);
178                    if (date == null) {
179                        date = RFC_1036.parse(dateString, pos);
180                        if (date == null)
181                            date = ASCTIME.parse(dateString, pos);
182                    }
183                }
184
185                if (date == null || date.getTime() > entry.lastModified) {
186                    entry = fetchURL(url, false);
187                    zipCache.put(url, entry);
188                }
189                if (date == null) {
190                    if (dateString == null)
191                        Debug.trace("Failed to retrieve request header: "
192                                    + url.toString());
193                    else
194                        Debug.trace("Failed to parse Last-Modified date: " +
195                                    dateString);
196                }
197
198           } else {
199                entry = fetchURL(url, false);
200                zipCache.put(url, entry);
201            }
202        } else {
203            if (url.getProtocol().equals("file")) {
204                entry = new Entry();
205                String path = url.getPath();
206
207                if (Utilities.isPlatformWindows) {
208                    String authority = url.getAuthority();
209                    if (authority != null) {
210                        path = authority + path;
211                    }
212                }
213                File f = new File(path);
214                entry.lastModified = f.lastModified();
215                try {
216                    entry.file = new ZipFile(f);
217                } catch (ZipException e) {
218                    error(new FileError("Failed to get cached ZipFile"
219                                        + " because " + e,
220                                        Pathname.makePathname(f)));
221                } catch (IOException e) {
222                    error(new FileError("Failed to get cached ZipFile"
223                                        + " because " + e,
224                                        Pathname.makePathname(f)));
225                }
226            } else {
227                entry = fetchURL(url, true);
228            }
229            zipCache.put(url, entry);
230        }
231        return entry.file;
232    }
233     
234    static private Entry fetchURL(URL url, boolean cached) {
235        Entry result = new Entry();
236        URL jarURL = null;
237        try {
238            jarURL = new URL("jar:" + url + "!/");
239        } catch (MalformedURLException e) {
240            error(new LispError("Failed to form a jar: URL from "
241                                + "'" + url + "'" 
242                                + " because " + e));
243        }
244        URLConnection connection = null;
245        try {
246            connection = jarURL.openConnection();
247        } catch (IOException e) {
248            error(new LispError("Failed to open "
249                                + "'" + jarURL + "'"
250                                + " with exception " 
251                                + e));
252        }
253        if (!(connection instanceof JarURLConnection)) {
254            error(new LispError("Could not get a URLConnection from " 
255                                + "'" + jarURL + "'"));
256        }
257        JarURLConnection jarURLConnection = (JarURLConnection) connection;
258        jarURLConnection.setUseCaches(cached);
259        try {
260            result.file = jarURLConnection.getJarFile();
261        } catch (IOException e) {
262            error(new LispError("Failed to fetch URL "
263                                 + "'" + jarURLConnection + "'"
264                                + " because " + e));
265        }
266        result.lastModified = jarURLConnection.getLastModified();
267        return result;
268    }
269
270    // ## remove-zip-cache-entry pathname => boolean
271    private static final Primitive REMOVE_ZIP_CACHE_ENTRY = new remove_zip_cache_entry();
272    private static class remove_zip_cache_entry extends Primitive { 
273        remove_zip_cache_entry() {
274            super("remove-zip-cache-entry", PACKAGE_SYS, true, "pathname");
275        }
276        @Override
277        public LispObject execute(LispObject arg) {
278            Pathname p = coerceToPathname(arg);
279            boolean result = ZipCache.remove(p);
280            return result ? T : NIL;
281        }
282    }
283     
284    synchronized public static boolean remove(URL url) {
285        Entry entry = zipCache.get(url);
286        if (entry != null) {
287            try {
288                entry.file.close();
289            } catch (IOException e) {}
290            zipCache.remove(entry);
291            return true;
292        }
293        return false;
294    }
295
296    synchronized public static boolean remove(Pathname p) {
297        URL url = Pathname.makeURL(p);
298        if (url == null) {
299            return false;
300        }
301        return ZipCache.remove(url);
302    }
303
304    synchronized public static boolean remove(File f) {
305        Pathname p = Pathname.makePathname(f);
306        return ZipCache.remove(p);
307    }
308}
Note: See TracBrowser for help on using the repository browser.