View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.common.config;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.net.MalformedURLException;
24  import java.net.URL;
25  import java.nio.file.Path;
26  import java.security.CodeSource;
27  import java.security.ProtectionDomain;
28  import java.util.Enumeration;
29  import java.util.List;
30  import java.util.Locale;
31  import java.util.Optional;
32  
33  import org.apache.logging.log4j.LogManager;
34  import org.mycore.common.MCRClassTools;
35  import org.mycore.common.MCRDeveloperTools;
36  import org.mycore.common.MCRUtils;
37  
38  import jakarta.servlet.ServletContext;
39  
40  /**
41   * This helper class determines in which directory to look for addition configuration files.
42   *
43   * The configuration directory can be set with the system property or environment variable <code>MCR.ConfigDir</code>.
44   *
45   * The directory path is build this way:
46   * <ol>
47   *  <li>System property <code>MCR.Home</code> defined
48   *      <ol>
49   *          <li><code>System.getProperty("MCR.Home")</code></li>
50   *          <li><code>{prefix+'-'}{appName}</code></li>
51   *      </ol>
52   *  </li>
53   *  <li>Windows:
54   *      <ol>
55   *          <li><code>%LOCALAPPDATA%</code></li>
56   *          <li>MyCoRe</li>
57   *          <li><code>{prefix+'-'}{appName}</code></li>
58   *      </ol>
59   *  </li>
60   *  <li>other systems
61   *      <ol>
62   *          <li><code>$HOME</code></li>
63   *          <li>.mycore</li>
64   *          <li><code>{prefix+'-'}{appName}</code></li>
65   *      </ol>
66   *  </li>
67   * </ol>
68   * 
69   * <code>{prefix}</code> can be defined by setting System property <code>MCR.DataPrefix</code>.
70   * <code>{appName}</code> is always lowercase String determined using this
71   * <ol>
72   *  <li>System property <code>MCR.AppName</code></li>
73   *  <li>System property <code>MCR.NameOfProject</code></li>
74   *  <li>Servlet Context Init Parameter <code>appName</code>
75   *  <li>Servlet Context Path (if not root context, {@link ServletContext#getContextPath()})</li>
76   *  <li>Servlet Context Name ({@link ServletContext#getServletContextName()}) with space characters removed</li>
77   *  <li>base name of jar including this class</li>
78   *  <li>the String <code>"default"</code></li>
79   * </ol>
80   * 
81   * @author Thomas Scheffler (yagee)
82   * @see System#getProperties()
83   * @since 2013.12
84   */
85  public class MCRConfigurationDir {
86  
87      public static final String DISABLE_CONFIG_DIR_PROPERTY = "MCR.DisableConfigDir";
88  
89      public static final String CONFIGURATION_DIRECTORY_PROPERTY = "MCR.ConfigDir";
90  
91      private static ServletContext SERVLET_CONTEXT = null;
92  
93      private static String APP_NAME = null;
94  
95      private static File getMyCoReDirectory() {
96          String mcrHome = System.getProperty("MCR.Home");
97          if (mcrHome != null) {
98              return new File(mcrHome);
99          }
100         //Windows Vista onwards:
101         String localAppData = isWindows() ? System.getenv("LOCALAPPDATA") : null;
102         //on every other platform
103         String userDir = System.getProperty("user.home");
104         String parentDir = localAppData != null ? localAppData : userDir;
105         return new File(parentDir, getConfigBaseName());
106     }
107 
108     private static boolean isWindows() {
109         return System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("windows");
110     }
111 
112     private static String getConfigBaseName() {
113         return isWindows() ? "MyCoRe" : ".mycore";
114     }
115 
116     private static String getSource(Class<MCRConfigurationDir> clazz) {
117         if (clazz == null) {
118             return null;
119         }
120         ProtectionDomain protectionDomain = clazz.getProtectionDomain();
121         CodeSource codeSource = protectionDomain.getCodeSource();
122         if (codeSource == null) {
123             System.err.println("Cannot get CodeSource.");
124             return null;
125         }
126         URL location = codeSource.getLocation();
127         String fileName = location.getFile();
128         File sourceFile = new File(fileName);
129         return sourceFile.getName();
130     }
131 
132     private static String getAppName() {
133         if (APP_NAME == null) {
134             APP_NAME = buildAppName();
135         }
136         return APP_NAME;
137     }
138 
139     private static String buildAppName() {
140         String appName = System.getProperty("MCR.AppName");
141         if (appName != null) {
142             return appName;
143         }
144         String nameOfProject = System.getProperty("MCR.NameOfProject");
145         if (nameOfProject != null) {
146             return nameOfProject;
147         }
148         if (SERVLET_CONTEXT != null) {
149             String servletAppName = SERVLET_CONTEXT.getInitParameter("appName");
150             if (servletAppName != null && !servletAppName.isEmpty()) {
151                 return servletAppName;
152             }
153             String contextPath = SERVLET_CONTEXT.getContextPath();
154             if (!contextPath.isEmpty()) {
155                 return contextPath.substring(1);//remove leading '/'
156             }
157             String servletContextName = SERVLET_CONTEXT.getServletContextName();
158             if (servletContextName != null
159                 && !(servletContextName.trim().isEmpty() || servletContextName.contains("/"))) {
160                 return servletContextName.replaceAll("\\s", "");
161             }
162         }
163         String sourceFileName = getSource(MCRConfigurationDir.class);
164         if (sourceFileName != null) {
165             int beginIndex = sourceFileName.lastIndexOf('.') - 1;
166             if (beginIndex > 0) {
167                 sourceFileName = sourceFileName.substring(0, beginIndex);
168             }
169             return sourceFileName.replaceAll("-\\d.*", "");//strips version 
170         }
171         return "default";
172     }
173 
174     private static String getPrefix() {
175         String dataPrefix = System.getProperty("MCR.DataPrefix");
176         return dataPrefix == null ? "" : dataPrefix + "-";
177     }
178 
179     static void setServletContext(ServletContext servletContext) {
180         SERVLET_CONTEXT = servletContext;
181         APP_NAME = null;
182     }
183 
184     /**
185      * Returns the configuration directory for this MyCoRe instance.
186      * @return null if System property {@value #DISABLE_CONFIG_DIR_PROPERTY} is set.
187      */
188     public static File getConfigurationDirectory() {
189         if (System.getProperties().containsKey(CONFIGURATION_DIRECTORY_PROPERTY)) {
190             return new File(System.getProperties().getProperty(CONFIGURATION_DIRECTORY_PROPERTY));
191         }
192         if (System.getenv().containsKey(CONFIGURATION_DIRECTORY_PROPERTY)) {
193             return new File(System.getenv(CONFIGURATION_DIRECTORY_PROPERTY));
194         }
195         if (!System.getProperties().containsKey(DISABLE_CONFIG_DIR_PROPERTY)) {
196             return new File(getMyCoReDirectory(), getPrefix() + getAppName());
197         }
198         return null;
199     }
200 
201     /**
202      * Returns a File object, if {@link #getConfigurationDirectory()} does not return <code>null</code>
203      * and directory exists.
204      * @param relativePath relative path to file or directory with configuration directory as base.
205      * @return null if configuration directory does not exist or is disabled.
206      */
207     public static File getConfigFile(String relativePath) {
208         File configurationDirectory = getConfigurationDirectory();
209         if (configurationDirectory == null || !configurationDirectory.isDirectory()) {
210             return null;
211         }
212 
213         return MCRUtils.safeResolve(configurationDirectory.toPath(), relativePath).toFile();
214     }
215 
216     /**
217      * Returns URL of a config resource.
218      * Same as {@link #getConfigResource(String, ClassLoader)} with second argument <code>null</code>.
219      * @param relativePath as defined in {@link #getConfigFile(String)}
220      */
221     public static URL getConfigResource(String relativePath) {
222         return getConfigResource(relativePath, null);
223     }
224 
225     /**
226      * Returns URL of a config resource.
227      * If {@link #getConfigFile(String)} returns an existing file for "resources"+{relativePath}, its URL is returned.
228      * In any other case this method returns the same as {@link ClassLoader#getResource(String)} 
229      * @param relativePath as defined in {@link #getConfigFile(String)}
230      * @param classLoader a classLoader to resolve the resource (see above), null defaults to this class' class loader
231      */
232     public static URL getConfigResource(String relativePath, ClassLoader classLoader) {
233         if (MCRDeveloperTools.overrideActive()) {
234             final Optional<Path> overriddenFilePath = MCRDeveloperTools.getOverriddenFilePath(relativePath, false);
235             if (overriddenFilePath.isPresent()) {
236                 try {
237                     return overriddenFilePath.get().toUri().toURL();
238                 } catch (MalformedURLException e) {
239                     // Ignore
240                 }
241             }
242         }
243 
244         File resolvedFile = getConfigFile("resources/" + relativePath);
245         if (resolvedFile != null && resolvedFile.exists()) {
246             try {
247                 return resolvedFile.toURI().toURL();
248             } catch (MalformedURLException e) {
249                 LogManager.getLogger(MCRConfigurationDir.class)
250                     .warn("Exception while returning URL for file: {}", resolvedFile, e);
251             }
252         }
253         return getClassPathResource(relativePath, classLoader == null ? MCRClassTools.getClassLoader() : classLoader);
254     }
255 
256     private static URL getClassPathResource(String relativePath, ClassLoader classLoader) {
257         URL currentUrl = classLoader.getResource(relativePath);
258         if (SERVLET_CONTEXT != null && currentUrl != null) {
259             Enumeration<URL> resources = null;
260             try {
261                 resources = classLoader.getResources(relativePath);
262             } catch (IOException e) {
263                 LogManager.getLogger(MCRConfigurationDir.class)
264                     .error("Error while retrieving resource: {}", relativePath, e);
265             }
266             if (resources != null) {
267                 File configDir = getConfigurationDirectory();
268                 String configDirURL = configDir.toURI().toString();
269                 @SuppressWarnings("unchecked")
270                 List<String> libsOrder = (List<String>) SERVLET_CONTEXT.getAttribute(ServletContext.ORDERED_LIBS);
271                 int pos = Integer.MAX_VALUE;
272                 while (resources.hasMoreElements()) {
273                     URL testURL = resources.nextElement();
274                     String testURLStr = testURL.toString();
275                     if (testURLStr.contains(configDirURL)) {
276                         return testURL; //configuration directory always wins
277                     }
278                     if (testURLStr.startsWith("file:")) {
279                         //local files should generally win
280                         pos = -1;
281                         currentUrl = testURL;
282                     }
283                     if (pos > 0 && libsOrder != null /*if <absolute-ordering> present in web.xml */) {
284                         for (int i = 0; i < libsOrder.size(); i++) {
285                             if (testURLStr.contains(libsOrder.get(i)) && i < pos) {
286                                 currentUrl = testURL;
287                                 pos = i;
288                             }
289                         }
290                     }
291                 }
292             }
293         }
294         return currentUrl;
295     }
296 }