001/*
002 * (C) Copyright 2012 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Stephane Lacoin
018 */
019package org.nuxeo.osgi.util.jar;
020
021import java.io.IOException;
022import java.lang.reflect.Field;
023import java.lang.reflect.InvocationTargetException;
024import java.lang.reflect.Method;
025import java.net.URL;
026import java.net.URLClassLoader;
027import java.util.List;
028import java.util.Map;
029import java.util.jar.JarFile;
030
031public class URLJarFileIntrospector {
032
033    protected Method factoryGetMethod;
034
035    protected Method factoryCloseMethod;
036
037    protected Field jarField;
038
039    protected Method getJarFileMethod;
040
041    Field ucpField;
042
043    Field lmapField;
044
045    Field loadersField;
046
047    Field jarFileFactoryField;
048
049    Object factory;
050
051    public URLJarFileIntrospector() throws URLJarFileIntrospectionError {
052        try {
053            ucpField = URLClassLoader.class.getDeclaredField("ucp");
054            ucpField.setAccessible(true);
055            Class<?> ucpClass = loadClass("sun.misc.URLClassPath");
056            lmapField = ucpClass.getDeclaredField("lmap");
057            lmapField.setAccessible(true);
058            loadersField = ucpClass.getDeclaredField("loaders");
059            loadersField.setAccessible(true);
060            Class<?> jarLoaderClass = loadClass("sun.misc.URLClassPath$JarLoader");
061            jarField = jarLoaderClass.getDeclaredField("jar");
062            jarField.setAccessible(true);
063            getJarFileMethod = jarLoaderClass.getDeclaredMethod("getJarFile", new Class<?>[] { URL.class });
064            getJarFileMethod.setAccessible(true);
065            Class<?> jarURLConnectionClass = loadClass("sun.net.www.protocol.jar.JarURLConnection");
066            jarFileFactoryField = jarURLConnectionClass.getDeclaredField("factory");
067            jarFileFactoryField.setAccessible(true);
068            factory = jarFileFactoryField.get(null);
069            Class<?> factoryClass = loadClass("sun.net.www.protocol.jar.JarFileFactory");
070            factoryGetMethod = factoryClass.getMethod("get", new Class<?>[] { URL.class });
071            factoryGetMethod.setAccessible(true);
072            factoryCloseMethod = factoryClass.getMethod("close", new Class<?>[] { JarFile.class });
073            factoryCloseMethod.setAccessible(true);
074        } catch (NoSuchFieldException | SecurityException | ClassNotFoundException | NoSuchMethodException
075                | IllegalArgumentException | IllegalAccessException cause) {
076            throw new URLJarFileIntrospectionError("Cannot introspect url class loader jar files", cause);
077        }
078    }
079
080    protected Object fetchFactory() throws URLJarFileIntrospectionError {
081        try {
082            return jarFileFactoryField.get(null);
083        } catch (IllegalArgumentException | IllegalAccessException cause) {
084            throw new URLJarFileIntrospectionError("Cannot access to factory", cause);
085        }
086    }
087
088    protected static Class<?> loadClass(String name) throws ClassNotFoundException {
089        return URLJarFileIntrospector.class.getClassLoader().loadClass(name);
090    }
091
092    public JarFileCloser newJarFileCloser(ClassLoader loader) throws URLJarFileIntrospectionError {
093        return new URLJarFileCloser(this, loader);
094    }
095
096    protected URLClassLoaderCloser newURLClassLoaderCloser(URLClassLoader loader) throws URLJarFileIntrospectionError {
097        try {
098            Object ucp = ucpField.get(loader);
099            Map<?, ?> index = (Map<?, ?>) lmapField.get(ucp);
100            List<?> loaders = (List<?>) loadersField.get(ucp);
101            return new URLClassLoaderCloser(this, index, loaders);
102        } catch (IllegalArgumentException | IllegalAccessException cause) {
103            throw new URLJarFileIntrospectionError("Cannot unwrap url class loader fields", cause);
104        }
105    }
106
107    public void close(URL location) throws IOException {
108        JarFile jar = null;
109        try {
110            jar = (JarFile) factoryGetMethod.invoke(factory, new Object[] { location });
111            factoryCloseMethod.invoke(factory, jar);
112        } catch (IllegalAccessException e) {
113            throw new RuntimeException("Cannot use reflection on jar file factory", e);
114        } catch (InvocationTargetException e) {
115            throw new RuntimeException("Cannot use reflection on jar file factory", e);
116        }
117        jar.close();
118    }
119}