package com.dbg.cloud.acheron.autoconfigure.admin; import com.dbg.cloud.acheron.adminendpoints.AdminChildContextConfiguration; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeansException; import org.springframework.beans.FatalBeanException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration; import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext; import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.EmbeddedWebApplicationContext; import org.springframework.boot.context.event.ApplicationFailedEvent; import org.springframework.context.*; import org.springframework.context.annotation.Configuration; import org.springframework.context.event.ContextClosedEvent; import org.springframework.core.env.Environment; import org.springframework.core.type.MethodMetadata; import org.springframework.web.context.WebApplicationContext; import java.lang.reflect.Modifier; @Configuration @Slf4j public class AcheronAdminEndpointsAutoConfiguration implements ApplicationContextAware, BeanFactoryAware, SmartInitializingSingleton { private ApplicationContext applicationContext; private BeanFactory beanFactory; @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } @Override public void afterSingletonsInstantiated() { // Determine admin port AdminServerPort adminPort = AdminServerPort.DIFFERENT; if (this.applicationContext instanceof WebApplicationContext) { adminPort = AdminServerPort.get(this.applicationContext.getEnvironment(), this.beanFactory); } if (adminPort == AdminServerPort.DIFFERENT) { // admin port is different than server port if (this.applicationContext instanceof EmbeddedWebApplicationContext && ((EmbeddedWebApplicationContext) this.applicationContext).getEmbeddedServletContainer() != null) { createChildAdminContext(); } else { log.error("Could not start embedded admin container."); } } else { log.error("Could not start embedded admin container as it is set up the same as server port."); } } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; log.info("Setting application context {}", applicationContext.getId()); } private void createChildAdminContext() { final AnnotationConfigEmbeddedWebApplicationContext childContext = new AnnotationConfigEmbeddedWebApplicationContext(); childContext.setParent(this.applicationContext); childContext.setNamespace("admin"); childContext.setId(this.applicationContext.getId() + ":admin"); childContext.setClassLoader(this.applicationContext.getClassLoader()); childContext.register( AdminChildContextConfiguration.class, PropertyPlaceholderAutoConfiguration.class, EmbeddedServletContainerAutoConfiguration.class, DispatcherServletAutoConfiguration.class); registerEmbeddedServletContainerFactory(childContext); CloseAdminContextListener.addIfPossible(this.applicationContext, childContext); childContext.refresh(); } private void registerEmbeddedServletContainerFactory( final AnnotationConfigEmbeddedWebApplicationContext childContext) { try { final ConfigurableListableBeanFactory beanFactory = childContext.getBeanFactory(); if (beanFactory instanceof BeanDefinitionRegistry) { final BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; registry.registerBeanDefinition("embeddedServletContainerFactory", new RootBeanDefinition(determineEmbeddedServletContainerFactoryClass())); } } catch (NoSuchBeanDefinitionException ex) { // Ignore and assume auto-configuration } } private Class<?> determineEmbeddedServletContainerFactoryClass() throws NoSuchBeanDefinitionException { final Class<?> servletContainerFactoryClass = this.applicationContext.getBean(EmbeddedServletContainerFactory.class).getClass(); if (cannotBeInstantiated(servletContainerFactoryClass)) { throw new FatalBeanException( "EmbeddedServletContainerFactory implementation " + servletContainerFactoryClass.getName() + " cannot be instantiated. To allow a separate admin port to be used, a top-level class " + "or static inner class should be used instead"); } return servletContainerFactoryClass; } private boolean cannotBeInstantiated(Class<?> clazz) { return clazz.isLocalClass() || (clazz.isMemberClass() && !Modifier.isStatic(clazz.getModifiers())) || clazz.isAnonymousClass(); } protected enum AdminServerPort { DISABLE, SAME, DIFFERENT; public static AdminServerPort get(final Environment environment, final BeanFactory beanFactory) { Integer serverPort = getPortProperty(environment, "server."); if (serverPort == null && hasCustomBeanDefinition(beanFactory, ServerProperties.class, ServerPropertiesAutoConfiguration.class)) { serverPort = getTemporaryBean(beanFactory, ServerProperties.class).getPort(); } Integer adminPort = getPortProperty(environment, "admin."); if (adminPort == null && hasCustomBeanDefinition(beanFactory, AdminServerProperties.class, AdminServerPropertiesAutoConfiguration.class)) { adminPort = getTemporaryBean(beanFactory, AdminServerProperties.class).getPort(); } if (adminPort != null && adminPort < 0) { return DISABLE; } return ((adminPort == null) || (serverPort == null && adminPort.equals(8080)) || (adminPort != 0 && adminPort.equals(serverPort)) ? SAME : DIFFERENT); } private static <T> T getTemporaryBean(final BeanFactory beanFactory, final Class<T> type) { if (!(beanFactory instanceof ConfigurableListableBeanFactory)) { return null; } final ConfigurableListableBeanFactory listable = (ConfigurableListableBeanFactory) beanFactory; final String[] names = listable.getBeanNamesForType(type, true, false); if (names == null || names.length != 1) { return null; } // Use a temporary child bean factory to avoid instantiating the bean in the // parent (it won't be bound to the environment yet) return createTemporaryBean(type, listable, listable.getBeanDefinition(names[0])); } private static <T> T createTemporaryBean(final Class<T> type, final ConfigurableListableBeanFactory parent, final BeanDefinition definition) { final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(parent); beanFactory.registerBeanDefinition(type.getName(), definition); return beanFactory.getBean(type); } private static Integer getPortProperty(final Environment environment, final String prefix) { final RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(environment, prefix); return resolver.getProperty("port", Integer.class); } private static <T> boolean hasCustomBeanDefinition(BeanFactory beanFactory, Class<T> type, Class<?> configClass) { if (!(beanFactory instanceof ConfigurableListableBeanFactory)) { return false; } return hasCustomBeanDefinition((ConfigurableListableBeanFactory) beanFactory, type, configClass); } private static <T> boolean hasCustomBeanDefinition(final ConfigurableListableBeanFactory beanFactory, final Class<T> type, final Class<?> configClass) { final String[] names = beanFactory.getBeanNamesForType(type, true, false); if (names == null || names.length != 1) { return false; } final BeanDefinition definition = beanFactory.getBeanDefinition(names[0]); if (definition instanceof AnnotatedBeanDefinition) { final MethodMetadata factoryMethodMetadata = ((AnnotatedBeanDefinition) definition).getFactoryMethodMetadata(); if (factoryMethodMetadata != null) { final String className = factoryMethodMetadata.getDeclaringClassName(); return !configClass.getName().equals(className); } } return true; } } private static class CloseAdminContextListener implements ApplicationListener<ApplicationEvent> { private final ApplicationContext parentContext; private final ConfigurableApplicationContext childContext; CloseAdminContextListener(final ApplicationContext parentContext, final ConfigurableApplicationContext childContext) { this.parentContext = parentContext; this.childContext = childContext; } @Override public void onApplicationEvent(final ApplicationEvent event) { if (event instanceof ContextClosedEvent) { onContextClosedEvent((ContextClosedEvent) event); } if (event instanceof ApplicationFailedEvent) { onApplicationFailedEvent((ApplicationFailedEvent) event); } } private void onContextClosedEvent(final ContextClosedEvent event) { propagateCloseIfNecessary(event.getApplicationContext()); } private void onApplicationFailedEvent(final ApplicationFailedEvent event) { propagateCloseIfNecessary(event.getApplicationContext()); } private void propagateCloseIfNecessary(final ApplicationContext applicationContext) { if (applicationContext == this.parentContext) { this.childContext.close(); } } public static void addIfPossible(final ApplicationContext parentContext, final ConfigurableApplicationContext childContext) { if (parentContext instanceof ConfigurableApplicationContext) { add((ConfigurableApplicationContext) parentContext, childContext); } } private static void add(final ConfigurableApplicationContext parentContext, final ConfigurableApplicationContext childContext) { parentContext.addApplicationListener(new CloseAdminContextListener(parentContext, childContext)); } } }