001/*
002 * Copyright 2012-2018 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.system;
018
019import java.io.File;
020import java.io.IOException;
021import java.io.InputStream;
022import java.net.JarURLConnection;
023import java.net.URL;
024import java.net.URLConnection;
025import java.security.CodeSource;
026import java.security.ProtectionDomain;
027import java.util.Enumeration;
028import java.util.jar.JarFile;
029import java.util.jar.Manifest;
030
031import org.springframework.util.ClassUtils;
032import org.springframework.util.StringUtils;
033
034/**
035 * Provides access to the application home directory. Attempts to pick a sensible home for
036 * both Jar Files, Exploded Archives and directly running applications.
037 *
038 * @author Phillip Webb
039 * @author Raja Kolli
040 * @since 2.0.0
041 */
042public class ApplicationHome {
043
044        private final File source;
045
046        private final File dir;
047
048        /**
049         * Create a new {@link ApplicationHome} instance.
050         */
051        public ApplicationHome() {
052                this(null);
053        }
054
055        /**
056         * Create a new {@link ApplicationHome} instance for the specified source class.
057         * @param sourceClass the source class or {@code null}
058         */
059        public ApplicationHome(Class<?> sourceClass) {
060                this.source = findSource((sourceClass != null) ? sourceClass : getStartClass());
061                this.dir = findHomeDir(this.source);
062        }
063
064        private Class<?> getStartClass() {
065                try {
066                        ClassLoader classLoader = getClass().getClassLoader();
067                        return getStartClass(classLoader.getResources("META-INF/MANIFEST.MF"));
068                }
069                catch (Exception ex) {
070                        return null;
071                }
072        }
073
074        private Class<?> getStartClass(Enumeration<URL> manifestResources) {
075                while (manifestResources.hasMoreElements()) {
076                        try (InputStream inputStream = manifestResources.nextElement().openStream()) {
077                                Manifest manifest = new Manifest(inputStream);
078                                String startClass = manifest.getMainAttributes().getValue("Start-Class");
079                                if (startClass != null) {
080                                        return ClassUtils.forName(startClass, getClass().getClassLoader());
081                                }
082                        }
083                        catch (Exception ex) {
084                        }
085                }
086                return null;
087        }
088
089        private File findSource(Class<?> sourceClass) {
090                try {
091                        ProtectionDomain domain = (sourceClass != null)
092                                        ? sourceClass.getProtectionDomain() : null;
093                        CodeSource codeSource = (domain != null) ? domain.getCodeSource() : null;
094                        URL location = (codeSource != null) ? codeSource.getLocation() : null;
095                        File source = (location != null) ? findSource(location) : null;
096                        if (source != null && source.exists() && !isUnitTest()) {
097                                return source.getAbsoluteFile();
098                        }
099                        return null;
100                }
101                catch (Exception ex) {
102                        return null;
103                }
104        }
105
106        private boolean isUnitTest() {
107                try {
108                        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
109                        for (int i = stackTrace.length - 1; i >= 0; i--) {
110                                if (stackTrace[i].getClassName().startsWith("org.junit.")) {
111                                        return true;
112                                }
113                        }
114                }
115                catch (Exception ex) {
116                }
117                return false;
118        }
119
120        private File findSource(URL location) throws IOException {
121                URLConnection connection = location.openConnection();
122                if (connection instanceof JarURLConnection) {
123                        return getRootJarFile(((JarURLConnection) connection).getJarFile());
124                }
125                return new File(location.getPath());
126        }
127
128        private File getRootJarFile(JarFile jarFile) {
129                String name = jarFile.getName();
130                int separator = name.indexOf("!/");
131                if (separator > 0) {
132                        name = name.substring(0, separator);
133                }
134                return new File(name);
135        }
136
137        private File findHomeDir(File source) {
138                File homeDir = source;
139                homeDir = (homeDir != null) ? homeDir : findDefaultHomeDir();
140                if (homeDir.isFile()) {
141                        homeDir = homeDir.getParentFile();
142                }
143                homeDir = homeDir.exists() ? homeDir : new File(".");
144                return homeDir.getAbsoluteFile();
145        }
146
147        private File findDefaultHomeDir() {
148                String userDir = System.getProperty("user.dir");
149                return new File(StringUtils.hasLength(userDir) ? userDir : ".");
150        }
151
152        /**
153         * Returns the underlying source used to find the home directory. This is usually the
154         * jar file or a directory. Can return {@code null} if the source cannot be
155         * determined.
156         * @return the underlying source or {@code null}
157         */
158        public File getSource() {
159                return this.source;
160        }
161
162        /**
163         * Returns the application home directory.
164         * @return the home directory (never {@code null})
165         */
166        public File getDir() {
167                return this.dir;
168        }
169
170        @Override
171        public String toString() {
172                return getDir().toString();
173        }
174
175}