001/*
002 * Copyright 2012-2017 the original author or authors.
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
017package org.springframework.boot.loader;
018
019import java.io.IOException;
020import java.net.JarURLConnection;
021import java.net.URL;
022import java.net.URLClassLoader;
023import java.net.URLConnection;
024import java.security.AccessController;
025import java.security.PrivilegedExceptionAction;
026import java.util.Enumeration;
027import java.util.jar.JarFile;
028
029import org.springframework.boot.loader.jar.Handler;
030import org.springframework.lang.UsesJava7;
031
032/**
033 * {@link ClassLoader} used by the {@link Launcher}.
034 *
035 * @author Phillip Webb
036 * @author Dave Syer
037 * @author Andy Wilkinson
038 */
039public class LaunchedURLClassLoader extends URLClassLoader {
040
041        static {
042                performParallelCapableRegistration();
043        }
044
045        /**
046         * Create a new {@link LaunchedURLClassLoader} instance.
047         * @param urls the URLs from which to load classes and resources
048         * @param parent the parent class loader for delegation
049         */
050        public LaunchedURLClassLoader(URL[] urls, ClassLoader parent) {
051                super(urls, parent);
052        }
053
054        @Override
055        public URL findResource(String name) {
056                Handler.setUseFastConnectionExceptions(true);
057                try {
058                        return super.findResource(name);
059                }
060                finally {
061                        Handler.setUseFastConnectionExceptions(false);
062                }
063        }
064
065        @Override
066        public Enumeration<URL> findResources(String name) throws IOException {
067                Handler.setUseFastConnectionExceptions(true);
068                try {
069                        return super.findResources(name);
070                }
071                finally {
072                        Handler.setUseFastConnectionExceptions(false);
073                }
074        }
075
076        @Override
077        protected Class<?> loadClass(String name, boolean resolve)
078                        throws ClassNotFoundException {
079                Handler.setUseFastConnectionExceptions(true);
080                try {
081                        try {
082                                definePackageIfNecessary(name);
083                        }
084                        catch (IllegalArgumentException ex) {
085                                // Tolerate race condition due to being parallel capable
086                                if (getPackage(name) == null) {
087                                        // This should never happen as the IllegalArgumentException indicates
088                                        // that the package has already been defined and, therefore,
089                                        // getPackage(name) should not return null.
090                                        throw new AssertionError("Package " + name + " has already been "
091                                                        + "defined but it could not be found");
092                                }
093                        }
094                        return super.loadClass(name, resolve);
095                }
096                finally {
097                        Handler.setUseFastConnectionExceptions(false);
098                }
099        }
100
101        /**
102         * Define a package before a {@code findClass} call is made. This is necessary to
103         * ensure that the appropriate manifest for nested JARs is associated with the
104         * package.
105         * @param className the class name being found
106         */
107        private void definePackageIfNecessary(String className) {
108                int lastDot = className.lastIndexOf('.');
109                if (lastDot >= 0) {
110                        String packageName = className.substring(0, lastDot);
111                        if (getPackage(packageName) == null) {
112                                try {
113                                        definePackage(className, packageName);
114                                }
115                                catch (IllegalArgumentException ex) {
116                                        // Tolerate race condition due to being parallel capable
117                                        if (getPackage(packageName) == null) {
118                                                // This should never happen as the IllegalArgumentException
119                                                // indicates that the package has already been defined and,
120                                                // therefore, getPackage(name) should not have returned null.
121                                                throw new AssertionError(
122                                                                "Package " + packageName + " has already been defined "
123                                                                                + "but it could not be found");
124                                        }
125                                }
126                        }
127                }
128        }
129
130        private void definePackage(final String className, final String packageName) {
131                try {
132                        AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
133                                @Override
134                                public Object run() throws ClassNotFoundException {
135                                        String packageEntryName = packageName.replace('.', '/') + "/";
136                                        String classEntryName = className.replace('.', '/') + ".class";
137                                        for (URL url : getURLs()) {
138                                                try {
139                                                        URLConnection connection = url.openConnection();
140                                                        if (connection instanceof JarURLConnection) {
141                                                                JarFile jarFile = ((JarURLConnection) connection)
142                                                                                .getJarFile();
143                                                                if (jarFile.getEntry(classEntryName) != null
144                                                                                && jarFile.getEntry(packageEntryName) != null
145                                                                                && jarFile.getManifest() != null) {
146                                                                        definePackage(packageName, jarFile.getManifest(),
147                                                                                        url);
148                                                                        return null;
149                                                                }
150                                                        }
151                                                }
152                                                catch (IOException ex) {
153                                                        // Ignore
154                                                }
155                                        }
156                                        return null;
157                                }
158                        }, AccessController.getContext());
159                }
160                catch (java.security.PrivilegedActionException ex) {
161                        // Ignore
162                }
163        }
164
165        /**
166         * Clear URL caches.
167         */
168        public void clearCache() {
169                for (URL url : getURLs()) {
170                        try {
171                                URLConnection connection = url.openConnection();
172                                if (connection instanceof JarURLConnection) {
173                                        clearCache(connection);
174                                }
175                        }
176                        catch (IOException ex) {
177                                // Ignore
178                        }
179                }
180
181        }
182
183        private void clearCache(URLConnection connection) throws IOException {
184                Object jarFile = ((JarURLConnection) connection).getJarFile();
185                if (jarFile instanceof org.springframework.boot.loader.jar.JarFile) {
186                        ((org.springframework.boot.loader.jar.JarFile) jarFile).clearCache();
187                }
188        }
189
190        @UsesJava7
191        private static void performParallelCapableRegistration() {
192                try {
193                        ClassLoader.registerAsParallelCapable();
194                }
195                catch (NoSuchMethodError ex) {
196                        // Running on Java 6. Continue.
197                }
198        }
199
200}