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.lang.reflect.InaccessibleObjectException;
23  import java.lang.reflect.InvocationTargetException;
24  import java.lang.reflect.Method;
25  import java.net.URI;
26  import java.net.URL;
27  import java.net.URLClassLoader;
28  import java.util.Locale;
29  import java.util.Map;
30  import java.util.Objects;
31  import java.util.Optional;
32  import java.util.Set;
33  import java.util.function.BiConsumer;
34  import java.util.function.Function;
35  import java.util.stream.Collectors;
36  import java.util.stream.Stream;
37  
38  import org.apache.logging.log4j.LogManager;
39  import org.apache.logging.log4j.core.LoggerContext;
40  import org.apache.logging.log4j.status.StatusLogger;
41  import org.apache.logging.log4j.web.Log4jServletContainerInitializer;
42  import org.mycore.common.MCRClassTools;
43  import org.mycore.common.MCRSessionMgr;
44  import org.mycore.common.events.MCRStartupHandler;
45  import org.mycore.common.events.MCRStartupHandler.AutoExecutable;
46  import org.mycore.common.log4j2.MCRSessionThreadContext;
47  
48  import jakarta.servlet.ServletContext;
49  import jakarta.servlet.ServletException;
50  
51  /**
52   * Called by {@link MCRStartupHandler} on start up to setup {@link MCRConfiguration2}.
53   *
54   * @author Thomas Scheffler (yagee)
55   * @since 2013.12
56   */
57  public class MCRConfigurationDirSetup implements AutoExecutable {
58  
59      private static final StatusLogger LOGGER = StatusLogger.getLogger();
60  
61      public static void loadExternalLibs() {
62          File resourceDir = MCRConfigurationDir.getConfigFile("resources");
63          if (resourceDir == null) {
64              //no configuration dir exists
65              return;
66          }
67          Optional<URLClassLoader> classLoaderOptional = Stream
68              .of(MCRClassTools.getClassLoader(), Thread.currentThread().getContextClassLoader())
69              .filter(URLClassLoader.class::isInstance)
70              .map(URLClassLoader.class::cast)
71              .findFirst();
72          if (classLoaderOptional.isEmpty()) {
73              System.err
74                  .println(classLoaderOptional.getClass() + " is unsupported for adding extending CLASSPATH at runtime.");
75              return;
76          }
77          File libDir = MCRConfigurationDir.getConfigFile("lib");
78          URLClassLoader urlClassLoader = classLoaderOptional.get();
79          Set<URL> currentCPElements = Stream.of(urlClassLoader.getURLs()).collect(Collectors.toSet());
80          try {
81              Class<? extends ClassLoader> classLoaderClass = urlClassLoader.getClass();
82              BiConsumer<ClassLoader, URL> addUrlMethod = addToClassPath(classLoaderClass);
83              getFileStream(resourceDir, libDir)
84                  .filter(Objects::nonNull)
85                  .map(File::toURI)
86                  .map(u -> {
87                      try {
88                          return u.toURL();
89                      } catch (Exception e) {
90                          // should never happen for "file://" URIS
91                          return null;
92                      }
93                  })
94                  .filter(Objects::nonNull)
95                  .filter(u -> !currentCPElements.contains(u))
96                  .peek(u -> System.out.println("Adding to CLASSPATH: " + u))
97                  .forEach(url -> addUrlMethod.accept(urlClassLoader, url));
98          } catch (InaccessibleObjectException | ReflectiveOperationException | SecurityException e) {
99              LogManager.getLogger(MCRConfigurationInputStream.class)
100                 .warn("{} does not support adding additional JARs at runtime", urlClassLoader.getClass(), e);
101         }
102     }
103 
104     /**
105      * Returns a BiConsumer that adds a URL to the classpath of a ClassLoader instance of <code>classLoaderClass</code>.
106      */
107     private static BiConsumer<ClassLoader, URL> addToClassPath(Class<? extends ClassLoader> classLoaderClass)
108         throws NoSuchMethodException {
109         Method method;
110         Function<URL, ?> argumentMapper;
111         final Method addURL = getDeclaredMethod(classLoaderClass, "addURL", URL.class);
112         //URLClassLoader does not allow to setAccessible(true) anymore in java >=16
113         if (addURL.trySetAccessible()) {
114             //works well in Tomcat
115             System.out.println("Using " + addURL + " to modify classpath.");
116             method = addURL;
117             argumentMapper = u -> u;
118         } else {
119             final Method jettyFallback = getDeclaredMethod(classLoaderClass, "addClassPath", String.class);
120             System.out.println("Using " + jettyFallback + " to modify classpath.");
121             argumentMapper = URL::toString;
122             method = jettyFallback;
123         }
124         Method finalMethod = method;
125         Function<URL, ?> finalArgumentMapper = argumentMapper;
126         return (cl, url) -> {
127             try {
128                 finalMethod.invoke(cl, finalArgumentMapper.apply(url));
129             } catch (IllegalAccessException | InvocationTargetException e) {
130                 LOGGER.error("Could not add {} to current classloader.", url, e);
131             }
132         };
133     }
134 
135     private static Method getDeclaredMethod(Class<? extends ClassLoader> clazz, String method, Class... args)
136         throws NoSuchMethodException {
137         try {
138             return clazz.getDeclaredMethod(method, args);
139         } catch (NoSuchMethodException e) {
140             try {
141                 if (ClassLoader.class.isAssignableFrom(clazz.getSuperclass())) {
142                     return getDeclaredMethod((Class<? extends ClassLoader>) clazz.getSuperclass(), method, args);
143                 }
144             } catch (NoSuchMethodException e2) {
145                 throw e;
146             }
147             throw e;
148         }
149     }
150 
151     private static Stream<File> getFileStream(File resourceDir, File libDir) {
152         Stream<File> toClassPath = Stream.of(resourceDir);
153         if (libDir.isDirectory()) {
154             File[] listFiles = libDir
155                 .listFiles((dir, name) -> name.toLowerCase(Locale.ROOT).endsWith(".jar"));
156             if (listFiles.length != 0) {
157                 toClassPath = Stream.concat(toClassPath, Stream.of(listFiles));
158             }
159         }
160         return toClassPath;
161     }
162 
163     /* (non-Javadoc)
164      * @see org.mycore.common.events.MCRStartupHandler.AutoExecutable#getName()
165      */
166     @Override
167     public String getName() {
168         return "Setup of MCRConfigurationDir";
169     }
170 
171     /* (non-Javadoc)
172      * @see org.mycore.common.events.MCRStartupHandler.AutoExecutable#getPriority()
173      */
174     @Override
175     public int getPriority() {
176         return Integer.MAX_VALUE - 100;
177     }
178 
179     /* (non-Javadoc)
180      * @see org.mycore.common.events.MCRStartupHandler.AutoExecutable#startUp(jakarta.servlet.ServletContext)
181      */
182     @Override
183     public void startUp(ServletContext servletContext) {
184         MCRConfigurationDir.setServletContext(servletContext);
185         loadExternalLibs();
186         MCRConfigurationLoader configurationLoader = MCRConfigurationLoaderFactory.getConfigurationLoader();
187         Map<String, String> properties = configurationLoader.load();
188         final Map<String, String> deprecated = configurationLoader.loadDeprecated();
189         MCRConfigurationBase.initialize(deprecated, properties, true);
190         if (servletContext != null) {
191             Log4jServletContainerInitializer log4jInitializer = new Log4jServletContainerInitializer();
192             try {
193                 log4jInitializer.onStartup(null, servletContext);
194             } catch (ServletException e) {
195                 System.err.println("Could not start Log4J2 context");
196             }
197         }
198         String configFileKey = "log4j.configurationFile";
199         URL log4j2ConfigURL = null;
200         if (System.getProperty(configFileKey) == null) {
201             log4j2ConfigURL = MCRConfigurationDir.getConfigResource("log4j2.xml");
202         }
203         LoggerContext logCtx;
204         if (log4j2ConfigURL == null) {
205             logCtx = (LoggerContext) LogManager.getContext(false);
206         } else {
207             logCtx = (LoggerContext) LogManager.getContext(null, false, URI.create(log4j2ConfigURL.toString()));
208         }
209         logCtx.reconfigure();
210         System.out.printf(Locale.ROOT, "Using Log4J2 configuration at: %s%n",
211             logCtx.getConfiguration().getConfigurationSource().getLocation());
212         MCRSessionMgr.addSessionListener(new MCRSessionThreadContext());
213     }
214 }