All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hotswap.agent.plugin.spring.files.XmlBeanDefinitionScannerAgent Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2013-2024 the HotswapAgent authors.
 *
 * This file is part of HotswapAgent.
 *
 * HotswapAgent is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 2 of the License, or (at your
 * option) any later version.
 *
 * HotswapAgent 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 General
 * Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with HotswapAgent. If not, see http://www.gnu.org/licenses/.
 */
package org.hotswap.agent.plugin.spring.files;

import org.hotswap.agent.logging.AgentLogger;
import org.hotswap.agent.plugin.spring.SpringPlugin;
import org.hotswap.agent.plugin.spring.core.BeanFactoryProcessor;
import org.hotswap.agent.plugin.spring.scanner.ClassPathBeanDefinitionScannerAgent;
import org.hotswap.agent.plugin.spring.utils.ResourceUtils;
import org.hotswap.agent.util.spring.util.ObjectUtils;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.support.*;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;

import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import static org.hotswap.agent.plugin.spring.utils.ResourceUtils.convertToClasspathURL;

/**
 * IMPORTANT: DON'T REFER TO THIS CLASS IN OTHER CLASS!!
 */
public class XmlBeanDefinitionScannerAgent {
    private static final AgentLogger LOGGER = AgentLogger.getLogger(XmlBeanDefinitionScannerAgent.class);

    public static final String PROPERTY_PLACEHOLDER_CONFIGURER = "org.springframework.beans.factory.config.PropertyPlaceholderConfigurer";
    public static final String PROPERTY_SOURCES_PLACEHOLDER_CONFIGURER = "org.springframework.context.support.PropertySourcesPlaceholderConfigurer";

    private static Map> beanFactoryToAgentMap = new ConcurrentHashMap<>();
    private static Map pathToAgent = new HashMap<>();
    private static boolean basePackageInited = false;

    // xmlReader for corresponding url
    private BeanDefinitionReader reader;

    // XML's URL the current XmlBeanDefinitionScannerAgent is responsible for
    private URL url;

    // Beans defined in the XML file (beanName -> beanClassName)
    private Map beansRegistered = new HashMap<>();

    // PropertyResourceConfigurer's locations defined in the XML file
    private Set propertyLocations = new HashSet<>();

    /**
     * Flag to check reload status.
     * In unit test we need to wait for reload finish before the test can continue. Set flag to true
     * in the test class and wait until the flag is false again.
     */
    public static boolean reloadFlag = false;

    public static void registerBean(String beanName, BeanDefinition beanDefinition) {
        XmlBeanDefinitionScannerAgent agent = findAgent(beanDefinition);
        if (agent == null) {
            LOGGER.trace("cannot find registered XmlBeanDefinitionScannerAgent for bean {}", beanName);
            return;
        }
        LOGGER.info("registering bean {} to XmlBeanDefinitionScannerAgent {}", beanName, agent.url);
        registerBeanName(agent, beanName, beanDefinition.getBeanClassName());
        registerPropertyLocations(agent, beanDefinition);
    }

    /**
     * need to ensure that when method is invoked first time , this class is not loaded,
     * so this class is will be loaded by appClassLoader
     */
    public static void registerXmlBeanDefinitionScannerAgent(XmlBeanDefinitionReader reader, Resource resource) {
        LOGGER.trace("registerXmlBeanDefinitionScannerAgent, reader: {}, resource: {}, beanFactory:{}", reader,
                resource, ObjectUtils.identityToString(reader.getRegistry()));
        BeanDefinitionRegistry beanDefinitionRegistry = reader.getRegistry();
        if (beanDefinitionRegistry instanceof DefaultListableBeanFactory) {
            DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanDefinitionRegistry;
            Map agentMap = beanFactoryToAgentMap.computeIfAbsent(defaultListableBeanFactory, k -> new HashMap<>());
            fillAgentInstance(reader, resource, agentMap);
            return;
        }
        fillAgentInstance(reader, resource, pathToAgent);
    }

    public static Set reloadXmlsAndGetBean(DefaultListableBeanFactory beanFactory, boolean propertiesChanged,
                                                   Map placeHolderXmlRelation,Set recreateBean, Set xmls) {
        LOGGER.debug("reloadXmlsAndGetBean, propertiesChanged: {}, placeHolderXmlRelation: {}, recreateBean: {}, xmls: {}",
                propertiesChanged, placeHolderXmlRelation, recreateBean, xmls);
        synchronized (xmls) {
            Set xmlResourcePaths = new HashSet<>();
            if (propertiesChanged) {
                xmlResourcePaths.addAll(placeHolderXmlRelation.values());
            }
            Set result = XmlBeanDefinitionScannerAgent.reloadXmls(beanFactory, xmls, xmlResourcePaths);
            // clear the xmls after the beanDefinition is refreshed.
            xmls.clear();
            return result;
        }
    }

    private static void fillAgentInstance(XmlBeanDefinitionReader reader, Resource resource, Map instances) {
        String path = ResourceUtils.getPath(resource);
        if (path == null) {
            return;
        }

        URL resourceUrl = null;
        try {
            resourceUrl = resource.getURL();
        } catch (IOException e) {
            // ignore
        }

        if (!instances.containsKey(path)) {
            instances.put(path, new XmlBeanDefinitionScannerAgent(reader, resourceUrl));
        }
    }

    public static Set reloadXmls(DefaultListableBeanFactory beanFactory, Set urls, Set resourcePaths) {
        Set agents = new HashSet<>(resourcePaths.size() + urls.size());
        for (String resourcePath : resourcePaths) {

            XmlBeanDefinitionScannerAgent xmlBeanDefinitionScannerAgent = getAgent(beanFactory, resourcePath);
            if (xmlBeanDefinitionScannerAgent == null) {
                LOGGER.warning("url " + resourcePath + " is not associated with any XmlBeanDefinitionScannerAgent, not reloading");
                continue;
            }
            try {
                LOGGER.trace("Reloading XML {} since property file changed: {}", xmlBeanDefinitionScannerAgent.url, resourcePath);
                if (agents.add(xmlBeanDefinitionScannerAgent)) {
                    xmlBeanDefinitionScannerAgent.clearCache();
                }
            } catch (org.springframework.beans.factory.parsing.BeanDefinitionParsingException e) {
                LOGGER.error("Reloading XML failed: {}", e.getMessage());
            }
        }
        for (URL url : urls) {
            XmlBeanDefinitionScannerAgent xmlBeanDefinitionScannerAgent = getAgent(beanFactory, convertToClasspathURL(url.getPath()));
            if (xmlBeanDefinitionScannerAgent == null) {
                LOGGER.warning("url " + url + " is not associated with any XmlBeanDefinitionScannerAgent, not reloading");
                continue;
            }
            try {
                LOGGER.trace("Reloading XML {} since xml file changed", url);
                if (agents.add(xmlBeanDefinitionScannerAgent)) {
                    xmlBeanDefinitionScannerAgent.clearCache();
                }
            } catch (org.springframework.beans.factory.parsing.BeanDefinitionParsingException e) {
                LOGGER.error("Reloading XML failed: {}", e.getMessage());
            }
        }
        Set beanNames = new HashSet<>();
        for (XmlBeanDefinitionScannerAgent agent : agents) {
            try {
                agent.reloadDefinition();
                beanNames.addAll(agent.beansRegistered.keySet());
            } catch (org.springframework.beans.factory.parsing.BeanDefinitionParsingException e) {
                LOGGER.error("Reloading XML failed: {}", e.getMessage());
            }
        }
        return beanNames;
    }

    private static XmlBeanDefinitionScannerAgent getAgent(DefaultListableBeanFactory beanFactory, String path) {
        Map instances = beanFactoryToAgentMap.get(beanFactory);
        if (instances != null) {
            XmlBeanDefinitionScannerAgent xmlBeanDefinitionScannerAgent = instances.get(path);
            if (xmlBeanDefinitionScannerAgent != null) {
                return xmlBeanDefinitionScannerAgent;
            }
        }
        return pathToAgent.get(path);
    }

    private static XmlBeanDefinitionScannerAgent findAgent(BeanDefinition beanDefinition) {
        if (!(beanDefinition instanceof AbstractBeanDefinition)) {
            LOGGER.debug("BeanDefinition [{}] is not an instance of AbstractBeanDefinition, ignore", beanDefinition);
            return null;
        }

        if (beanDefinition instanceof AnnotatedBeanDefinition) {
            LOGGER.debug("BeanDefinition [{}] is an instance of AnnotatedBeanDefinition, ignore", beanDefinition);
            return null;
        }

        Resource resource = ((AbstractBeanDefinition) beanDefinition).getResource();
        if (resource == null) {
            LOGGER.debug("BeanDefinition [{}] has no resource, ignore", beanDefinition);
            return null;
        }

        try {
            if (resource instanceof ByteArrayResource) {
                LOGGER.debug("BeanDefinition [{}] has ByteArrayResource as resource, ignore. {}", beanDefinition,
                        new String(((ByteArrayResource) resource).getByteArray()));
                return null;
            }
            String path = convertToClasspathURL(resource.getURL().getPath());
            if (path == null) {
                return null;
            }
            return pathToAgent.get(path);
        } catch (IOException e) {
            LOGGER.warning("Fail to fetch url from resource: {}", resource);
            return null;
        }
    }

    private static void registerBeanName(XmlBeanDefinitionScannerAgent agent, String beanName, String beanClassName) {
        agent.beansRegistered.put(beanName, beanClassName == null ? "" : beanClassName);
    }

    private static void registerPropertyLocations(XmlBeanDefinitionScannerAgent agent, BeanDefinition beanDefinition) {
        String clazz = beanDefinition.getBeanClassName();
        if (!PROPERTY_PLACEHOLDER_CONFIGURER.equals(clazz) && !PROPERTY_SOURCES_PLACEHOLDER_CONFIGURER.equals(clazz)) {
            return;
        }

        PropertyValue pv = beanDefinition.getPropertyValues().getPropertyValue("location");
        if (pv != null && pv.getValue() instanceof TypedStringValue) {
            String location = ((TypedStringValue) pv.getValue()).getValue();
            if (location != null) {
                agent.propertyLocations.add(convertPropertyLocation(location));
            }
        }

        pv = beanDefinition.getPropertyValues().getPropertyValue("locations");
        if (pv != null && pv.getValue() instanceof ManagedList) {
            for (Object o : (ManagedList) pv.getValue()) {
                TypedStringValue value = (TypedStringValue) o;
                String location = value.getValue();
                if (location == null) {
                    continue;
                }

                agent.propertyLocations.add(convertPropertyLocation(location));
            }
        }
    }

    private static String convertPropertyLocation(String location) {
        if (location.startsWith("classpath:")) {
            location = location.substring("classpath:".length());
        } else {
            location = convertToClasspathURL(location);
        }
        return location;
    }

    private XmlBeanDefinitionScannerAgent(BeanDefinitionReader reader, URL url) {
        this.reader = reader;
        this.url = url;

        if (SpringPlugin.basePackagePrefixes != null && !basePackageInited) {
            ClassPathBeanDefinitionScannerAgent xmlBeanDefinitionScannerAgent = ClassPathBeanDefinitionScannerAgent.getInstance(new ClassPathBeanDefinitionScanner(reader.getRegistry()));
            for (String basePackage : SpringPlugin.basePackagePrefixes) {
                xmlBeanDefinitionScannerAgent.registerBasePackage(basePackage);
            }
            basePackageInited = true;
        }
    }

    void clearCache() {
        DefaultListableBeanFactory factory = maybeRegistryToBeanFactory();
        if (factory == null) {
            LOGGER.warning("Fail to find bean factory for url {}, cannot reload", this.url);
            return;
        }
        removeRegisteredBeanDefinitions(factory);
    }

    void reloadDefinition() {

        LOGGER.info("Reloading XML file '{}' of {} ", url, ObjectUtils.identityToString(this.reader.getRegistry()));
        // this will call registerBeanDefinition which in turn call resetBeanDefinition to destroy singleton
        // maybe should use watchResourceClassLoader.getResource?
        this.reader.loadBeanDefinitions(new FileSystemResource(url.getPath()));

        reloadFlag = false;
    }

    private void removeRegisteredBeanDefinitions(DefaultListableBeanFactory factory) {
        LOGGER.debug("Remove all beans defined in the XML file {} before reloading it", url.getPath());
        for (String beanName : beansRegistered.keySet()) {
            try {
                BeanFactoryProcessor.removeBeanDefinition(factory, beanName);
            } catch (NoSuchBeanDefinitionException e) {
                LOGGER.debug("Bean {} not found in factory, ignore", beanName);
            }
        }

        beansRegistered.clear();
    }

    private DefaultListableBeanFactory maybeRegistryToBeanFactory() {
        BeanDefinitionRegistry registry = this.reader.getRegistry();
        if (registry instanceof DefaultListableBeanFactory) {
            return (DefaultListableBeanFactory) registry;
        } else if (registry instanceof GenericApplicationContext) {
            return ((GenericApplicationContext) registry).getDefaultListableBeanFactory();
        }
        return null;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy