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}