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.events;
20  
21  import java.util.Arrays;
22  import java.util.Comparator;
23  import java.util.List;
24  import java.util.Objects;
25  import java.util.concurrent.ConcurrentSkipListSet;
26  import java.util.concurrent.TimeUnit;
27  import java.util.concurrent.locks.ReentrantReadWriteLock;
28  import java.util.stream.Stream;
29  
30  import org.apache.logging.log4j.LogManager;
31  import org.apache.logging.log4j.Logger;
32  import org.mycore.common.MCRException;
33  import org.mycore.common.MCRSessionMgr;
34  import org.mycore.common.config.MCRConfiguration2;
35  import org.mycore.common.config.MCRConfigurationException;
36  
37  import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor;
38  
39  /**
40   * is a wrapper for shutdown hooks. When used inside a web application this shutdown hook is bound to the
41   * ServletContext. If not this hook is bound to the Java Runtime. Every <code>Closeable</code> that is added via
42   * <code>addCloseable()</code> will be closed at shutdown time. Do not forget to remove any closeable via
43   * <code>removeCloseable()</code> to remove any instances. For registering this hook for a web application see
44   * <code>MCRServletContextListener</code>
45   *
46   * @author Thomas Scheffler (yagee)
47   * @see org.mycore.common.events.MCRShutdownThread
48   * @see org.mycore.common.events.MCRServletContextListener
49   * @since 1.3
50   */
51  public class MCRShutdownHandler {
52  
53      private static final int ADD_CLOSEABLE_TIMEOUT = 10;
54  
55      private static final String PROPERTY_SYSTEM_NAME = "MCR.CommandLineInterface.SystemName";
56  
57      private static MCRShutdownHandler SINGLETON = new MCRShutdownHandler();
58  
59      private final ConcurrentSkipListSet<Closeable> requests = new ConcurrentSkipListSet<>();
60  
61      private final ReentrantReadWriteLock shutdownLock = new ReentrantReadWriteLock();
62  
63      private volatile boolean shuttingDown = false;
64  
65      boolean isWebAppRunning;
66  
67      ClassLoaderLeakPreventor leakPreventor;
68  
69      private MCRShutdownHandler() {
70          isWebAppRunning = false;
71      }
72  
73      private void init() {
74          if (!isWebAppRunning) {
75              MCRShutdownThread.getInstance();
76          }
77      }
78  
79      public static MCRShutdownHandler getInstance() {
80          return SINGLETON;
81      }
82  
83      public void addCloseable(MCRShutdownHandler.Closeable c) {
84          Objects.requireNonNull(c);
85          init();
86          boolean hasShutDownLock;
87          try {
88              hasShutDownLock = shutdownLock.readLock().tryLock(ADD_CLOSEABLE_TIMEOUT, TimeUnit.SECONDS);
89          } catch (InterruptedException e) {
90              throw new MCRException("Could not aquire shutdown lock in time", e);
91          }
92          try {
93              if (hasShutDownLock && !shuttingDown) {
94                  requests.add(c);
95              } else {
96                  throw new MCRException("Cannot register Closeable while shutting down application.");
97              }
98          } finally {
99              if (hasShutDownLock) {
100                 shutdownLock.readLock().unlock();
101             }
102         }
103     }
104 
105     public void removeCloseable(MCRShutdownHandler.Closeable c) {
106         Objects.requireNonNull(c);
107         if (!shuttingDown) {
108             requests.remove(c);
109         }
110     }
111 
112     void shutDown() {
113         Logger logger = LogManager.getLogger();
114         String cfgSystemName = "MyCoRe:";
115         try {
116             cfgSystemName = MCRConfiguration2.getStringOrThrow(PROPERTY_SYSTEM_NAME) + ":";
117         } catch (MCRConfigurationException e) {
118             //may occur early if there is an error starting mycore up or in JUnit tests
119             logger.warn("Error getting '" + PROPERTY_SYSTEM_NAME + "': {}", e.getMessage());
120         }
121         final String system = cfgSystemName;
122         System.out.println(system + " Shutting down system, please wait...\n");
123         runClosables();
124         System.out.println(system + " closing any remaining MCRSession instances, please wait...\n");
125         MCRSessionMgr.close();
126         System.out.println(system + " Goodbye, and remember: \"Alles wird gut.\"\n");
127         LogManager.shutdown();
128         SINGLETON = null;
129         // may be needed in webapp to release file handles correctly.
130         if (leakPreventor != null) {
131             ClassLoaderLeakPreventor myLeakPreventor = leakPreventor;
132             leakPreventor = null;
133             myLeakPreventor.runCleanUps();
134         }
135     }
136 
137     void runClosables() {
138         Logger logger = LogManager.getLogger();
139         logger.debug(() -> "requests: " + requests);
140         Closeable[] closeables = requests.stream().toArray(Closeable[]::new);
141         Stream.of(closeables)
142             .peek(c -> logger.debug("Prepare Closing (1): {}", c))
143             .forEach(Closeable::prepareClose); //may add more Closeables MCR-1726
144         shutdownLock.writeLock().lock();
145         try {
146             shuttingDown = true;
147             //during shut down more request may come in MCR-1726
148             final List<Closeable> alreadyPrepared = Arrays.asList(closeables);
149             requests.stream()
150                 .filter(c -> !alreadyPrepared.contains(c))
151                 .peek(c -> logger.debug("Prepare Closing (2): {}", c))
152                 .forEach(Closeable::prepareClose);
153 
154             requests.stream()
155                 .peek(c -> logger.debug("Closing: {}", c))
156                 .forEach(Closeable::close);
157         } finally {
158             shutdownLock.writeLock().unlock();
159         }
160     }
161 
162     /**
163      * Object is cleanly closeable via <code>close()</code>-call.
164      *
165      * @author Thomas Scheffler (yagee)
166      */
167     @FunctionalInterface
168     public interface Closeable extends Comparable<Closeable> {
169         /**
170          * The default priority
171          */
172         int DEFAULT_PRIORITY = 5;
173 
174         /**
175          * prepare for closing this object that implements <code>Closeable</code>. This is the first part of the closing
176          * process. As a object may need database access to close cleanly this method can be used to be ahead of
177          * database outtake.
178          */
179         default void prepareClose() {
180             //should be overwritten if needed;
181         }
182 
183         /**
184          * cleanly closes this object that implements <code>Closeable</code>. You can provide some functionality to
185          * close open files and sockets or so.
186          */
187         void close();
188 
189         /**
190          * Returns the priority. A Closeable with a higher priority will be closed before a Closeable with a lower
191          * priority. Default priority is 5.
192          */
193         default int getPriority() {
194             return DEFAULT_PRIORITY;
195         }
196 
197         @Override
198         default int compareTo(Closeable other) {
199             //MCR-1941: never return 0 if !this.equals(other)
200             return Comparator.comparingInt(Closeable::getPriority)
201                 .thenComparingLong(Closeable::hashCode)
202                 .compare(other, this);
203         }
204     }
205 
206 }