/** * diqube: Distributed Query Base. * * Copyright (C) 2015 Bastian Gloeckle * * This file is part of diqube. * * diqube is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.diqube.context.shutdown; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.stream.Collectors; import java.util.stream.Stream; import org.diqube.util.TopologicalSort; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; /** * Utility class to handle calls of {@link ContextShutdownListener} of a {@link ApplicationContext} just before * {@link ConfigurableApplicationContext#close() closing} it. * * @author Bastian Gloeckle */ public class ShutdownUtil { private static final Logger logger = LoggerFactory.getLogger(ShutdownUtil.class); private ApplicationContext context; public ShutdownUtil(ApplicationContext context) { this.context = context; } /** * Call {@link ContextShutdownListener#contextAboutToShutdown()} of all interested beans in a way that honors * {@link ShutdownAfter} and {@link ShutdownBefore} annotations. */ public void callShutdownListeners() { List<ContextShutdownListener> allListeners = context.getBeansOfType(ContextShutdownListener.class).values().stream() .sorted((b1, b2) -> b1.getClass().getName().compareTo(b2.getClass().getName())).collect(Collectors.toList()); // find the single method in ConextShutdownListener - its the one that is not available in Object. We will inspect // that method for the annotation! Method shutdownMethod = Stream.of(ContextShutdownListener.class.getMethods()) .filter(m -> getMethodOrNull(Object.class, m.getName(), m.getParameterTypes()) == null).findAny().get(); Map<Integer, List<Integer>> successors = new HashMap<>(); // inspect annotations and find the classes which have to be shut down BEFORE a specific one. Map<Integer, List<Integer>> predecessors = new HashMap<>(); for (ContextShutdownListener listener : allListeners) { try { Method curMethod = listener.getClass().getMethod(shutdownMethod.getName(), shutdownMethod.getParameterTypes()); ShutdownAfter afterAnnotation = curMethod.getAnnotation(ShutdownAfter.class); List<Integer> beforeIndices = new ArrayList<>(); if (afterAnnotation != null) { for (Class<?> beforeCls : afterAnnotation.value()) { if (!(ContextShutdownListener.class.isAssignableFrom(beforeCls))) { logger.warn("Class '{}' wanted to be shut down after '{}', but '{}' is no {}.", listener.getClass().getName(), beforeCls.getName(), beforeCls.getName(), ContextShutdownListener.class.getSimpleName()); continue; } for (int i = 0; i < allListeners.size(); i++) if (beforeCls.isAssignableFrom(allListeners.get(i).getClass())) beforeIndices.add(i); } } predecessors.put(allListeners.indexOf(listener), beforeIndices); ShutdownBefore beforeAnnotation = curMethod.getAnnotation(ShutdownBefore.class); if (beforeAnnotation != null) { List<Integer> afterIndices = new ArrayList<>(); for (Class<?> afterClass : beforeAnnotation.value()) { if (!(ContextShutdownListener.class.isAssignableFrom(afterClass))) { logger.warn("Class '{}' wanted to be shut down before '{}', but '{}' is no {}.", listener.getClass().getName(), afterClass.getName(), afterClass.getName(), ContextShutdownListener.class.getSimpleName()); continue; } for (int i = 0; i < allListeners.size(); i++) if (afterClass.isAssignableFrom(allListeners.get(i).getClass())) afterIndices.add(i); } successors.put(allListeners.indexOf(listener), afterIndices); } } catch (NoSuchMethodException e) { // swallow, cannot happen, since all ContextShutdownListener s MUST have the shutdownMethod. } } // Turn around the predecessors and find out which classes need to be shut down AFTER a specific one. for (Entry<Integer, List<Integer>> predEntry : predecessors.entrySet()) { for (int beforeIdx : predEntry.getValue()) { if (!successors.containsKey(beforeIdx)) successors.put(beforeIdx, new ArrayList<>()); successors.get(beforeIdx).add(predEntry.getKey()); } } // exeucte Topological sort on the classes, so we will execute the shutdown in the right order. TopologicalSort<ContextShutdownListener> topSort = new TopologicalSort<>(listener -> { int idx = allListeners.indexOf(listener); if (!successors.containsKey(idx)) return new ArrayList<>(); return successors.get(idx).stream().map(startIdx -> allListeners.get(startIdx)).collect(Collectors.toList()); } , listener -> (long) allListeners.indexOf(listener), null); List<ContextShutdownListener> sortedListeners = topSort.sort(allListeners); // exeucte shutdown. for (ContextShutdownListener listener : sortedListeners) listener.contextAboutToShutdown(); } private Method getMethodOrNull(Class<?> clazz, String methodName, Class<?>... methodParameterTypes) { try { return clazz.getMethod(methodName, methodParameterTypes); } catch (NoSuchMethodException e) { return null; } } }