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