io.microsphere.spring.config.context.annotation.PropertySourceExtensionLoader Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.microsphere.spring.config.context.annotation;
import io.microsphere.spring.config.env.event.PropertySourceChangedEvent;
import io.microsphere.spring.config.env.event.PropertySourcesChangedEvent;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertyResolver;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.ObjectUtils;
import org.springframework.util.PathMatcher;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import static io.microsphere.spring.config.env.event.PropertySourceChangedEvent.added;
import static io.microsphere.spring.config.env.event.PropertySourceChangedEvent.replaced;
import static io.microsphere.text.FormatUtils.format;
import static io.microsphere.util.StringUtils.EMPTY_STRING_ARRAY;
import static java.util.Collections.emptyList;
import static java.util.Collections.singleton;
import static java.util.Collections.sort;
import static org.springframework.util.StringUtils.hasText;
/**
* Abstract {@link ImportSelector} class to load the {@link PropertySource PropertySource}
* when the {@link Configuration configuration} annotated the Enable annotation that meta-annotates {@link PropertySourceExtension @PropertySourceExtension}
*
* @param The type of {@link Annotation} must meta-annotate {@link PropertySourceExtension}
* @param The {@link PropertySourceExtensionAttributes} or its subtype
* @author Mercy
* @see PropertySourceExtension
* @see PropertySourceExtensionAttributes
* @see ImportSelector
* @since 1.0.0
*/
public abstract class PropertySourceExtensionLoader>
extends AnnotatedPropertySourceLoader implements ApplicationContextAware {
private final Class extensionAttributesType;
private final PathMatcher resourceMatcher;
private ApplicationContext context;
public PropertySourceExtensionLoader() {
super();
this.extensionAttributesType = resolveExtensionAttributesType();
this.resourceMatcher = new AntPathMatcher();
}
protected Class resolveExtensionAttributesType() {
ResolvableType type = ResolvableType.forType(this.getClass());
ResolvableType superType = type.as(PropertySourceExtensionLoader.class);
return (Class) superType.resolveGeneric(1);
}
/**
* Test the resource value is pattern or not
*
* @param resourceValue the {@link String} value presenting resource
* @return true
if resourceValue
is a pattern value
*/
public boolean isResourcePattern(String resourceValue) {
return this.resourceMatcher.isPattern(resourceValue);
}
/**
* Get the type of {@link EA}
*
* @return non-null
* @see PropertySourceExtensionAttributes
*/
public final Class getExtensionAttributesType() {
return extensionAttributesType;
}
@Override
protected final void loadPropertySource(AnnotationAttributes attributes, AnnotationMetadata metadata, String propertySourceName,
MutablePropertySources propertySources) throws Throwable {
Class annotationType = getAnnotationType();
Class extensionAttributesClass = getExtensionAttributesType();
ConfigurableEnvironment environment = getEnvironment();
EA extensionAttributes = buildExtensionAttributes(annotationType, extensionAttributesClass, attributes, environment);
PropertySource> propertySource = loadPropertySource(extensionAttributes, propertySourceName);
if (propertySource == null) {
String message = format("The PropertySources' Resource can't be found by the {} that annotated on {}", extensionAttributes, metadata.getClassName());
if (extensionAttributes.isIgnoreResourceNotFound()) {
logger.warn(message);
} else {
throw new IllegalArgumentException(message);
}
} else {
addPropertySource(extensionAttributes, propertySources, propertySource);
}
}
/**
* Add the {@link PropertySource} into {@link PropertySources} via {@link EA}
*
* @param extensionAttributes the {@link PropertySourceExtensionAttributes annotation attributes} of {@link PropertySourceExtension}
* @param propertySources the {@link MutablePropertySources} to be added
* @param propertySource the {@link PropertySource} is about to add
*/
protected void addPropertySource(EA extensionAttributes, MutablePropertySources propertySources, PropertySource> propertySource) {
if (extensionAttributes.isFirstPropertySource()) {
propertySources.addFirst(propertySource);
} else {
String relativePropertySourceName = extensionAttributes.getAfterPropertySourceName();
if (hasText(relativePropertySourceName)) {
propertySources.addAfter(relativePropertySourceName, propertySource);
} else {
relativePropertySourceName = extensionAttributes.getBeforePropertySourceName();
}
if (hasText(relativePropertySourceName)) {
propertySources.addBefore(relativePropertySourceName, propertySource);
} else {
propertySources.addLast(propertySource);
}
}
}
/**
* Builds an instance of {@link EA}
*
* @param annotationType the type of {@link A}
* @param extensionAttributesType the type of {@link EA}
* @param attributes {@link AnnotationAttributes}
* @param environment {@link ConfigurableEnvironment}
* @return non-null
* @throws Throwable
*/
protected EA buildExtensionAttributes(Class annotationType, Class extensionAttributesType, AnnotationAttributes attributes, ConfigurableEnvironment environment) throws Throwable {
Constructor constructor = extensionAttributesType.getConstructor(Map.class, Class.class, PropertyResolver.class);
return (EA) constructor.newInstance(attributes, annotationType, environment);
}
protected final PropertySource> loadPropertySource(EA extensionAttributes, String propertySourceName) throws Throwable {
Comparator resourceComparator = createResourceComparator(extensionAttributes, propertySourceName);
List propertySourceResources = resolvePropertySourceResources(extensionAttributes, propertySourceName, resourceComparator);
int propertySourceResourcesSize = propertySourceResources.size();
if (propertySourceResourcesSize < 1) { // No PropertySourceResource found
return null;
}
PropertySourceFactory factory = createPropertySourceFactory(extensionAttributes);
CompositePropertySource compositePropertySource = new CompositePropertySource(propertySourceName);
// Add Resources' PropertySource
for (int i = 0; i < propertySourceResourcesSize; i++) {
PropertySourceResource propertySourceResource = propertySourceResources.get(i);
ResourcePropertySource resourcePropertySource = createResourcePropertySource(extensionAttributes, propertySourceName, factory, propertySourceResource);
compositePropertySource.addPropertySource(resourcePropertySource);
}
if (extensionAttributes.isAutoRefreshed()) {
ResourcePropertySourcesRefresher resourcePropertySourcesRefresher = createResourcePropertySourcesRefresher(
extensionAttributes, propertySourceName, factory, resourceComparator);
configureResourcePropertySourcesRefresher(extensionAttributes, propertySourceResources, compositePropertySource,
resourcePropertySourcesRefresher);
}
return compositePropertySource;
}
private ResourcePropertySourcesRefresher createResourcePropertySourcesRefresher(EA extensionAttributes, String propertySourceName,
PropertySourceFactory factory,
Comparator resourceComparator) throws Throwable {
return (resourceValue, resource) -> {
CompositePropertySource compositePropertySource = getPropertySource(propertySourceName);
if (compositePropertySource == null) {
return;
}
List subEvents = new LinkedList<>();
if (resource == null) { // No Resource specified
refreshPropertySources(extensionAttributes, propertySourceName, factory, resourceComparator, resourceValue, compositePropertySource, subEvents);
} else {
refreshPropertySources(extensionAttributes, propertySourceName, factory, resourceComparator, resourceValue, resource, compositePropertySource, subEvents);
}
publishPropertySourcesChangedEvent(subEvents);
};
}
private void publishPropertySourcesChangedEvent(List subEvents) {
PropertySourcesChangedEvent propertySourcesChangedEvent = new PropertySourcesChangedEvent(this.context, subEvents);
this.context.publishEvent(propertySourcesChangedEvent);
}
private void refreshPropertySources(EA extensionAttributes, String propertySourceName, PropertySourceFactory factory,
Comparator resourceComparator, String resourceValue,
CompositePropertySource compositePropertySource, List subEvents) throws Throwable {
// Resolve the target PropertySourceResources
List propertySourceResources = resolvePropertySourceResources(extensionAttributes, propertySourceName, resourceValue, resourceComparator);
int propertySourceResourcesSize = propertySourceResources.size();
if (propertySourceResourcesSize < 1) {
return;
}
List resourcePropertySources = getResourcePropertySources(compositePropertySource);
List newResourcePropertySources = new ArrayList<>(propertySourceResourcesSize);
for (int i = 0; i < propertySourceResourcesSize; i++) {
PropertySourceResource propertySourceResource = propertySourceResources.get(i);
ResourcePropertySource newResourcePropertySource = createResourcePropertySource(extensionAttributes, propertySourceName, factory, propertySourceResource);
newResourcePropertySources.add(newResourcePropertySource);
}
updateResourcePropertySources(newResourcePropertySources, resourcePropertySources, subEvents);
updatePropertySources(propertySourceName, resourcePropertySources);
}
private void refreshPropertySources(EA extensionAttributes, String propertySourceName, PropertySourceFactory factory,
Comparator resourceComparator, String resourceValue, Resource resource,
CompositePropertySource compositePropertySource, List subEvents) throws Throwable {
PropertySourceResource propertySourceResource = createPropertySourceResource(resourceValue, resource, resourceComparator);
ResourcePropertySource newResourcePropertySource = createResourcePropertySource(extensionAttributes, propertySourceName, factory, propertySourceResource);
List resourcePropertySources = getResourcePropertySources(compositePropertySource);
updateResourcePropertySources(singleton(newResourcePropertySource), resourcePropertySources, subEvents);
updatePropertySources(propertySourceName, resourcePropertySources);
}
private void updatePropertySources(String propertySourceName, List resourcePropertySources) {
// Sort all ResourcePropertySources
sort(resourcePropertySources);
// Add all ResourcePropertySources into new CompositePropertySource
CompositePropertySource newCompositePropertySource = new CompositePropertySource(propertySourceName);
resourcePropertySources.forEach(newCompositePropertySource::addPropertySource);
// Add new CompositePropertySource
MutablePropertySources propertySources = getPropertySources();
propertySources.replace(propertySourceName, newCompositePropertySource);
}
private void updateResourcePropertySources(Iterable newResourcePropertySources,
List resourcePropertySources, List subEvents) {
for (ResourcePropertySource newResourcePropertySource : newResourcePropertySources) {
String newResourcePropertySourceName = newResourcePropertySource.getName();
Iterator iterator = resourcePropertySources.iterator();
boolean addedSubEvent = false;
while (iterator.hasNext()) {
ResourcePropertySource resourcePropertySource = iterator.next();
// Remove the old ResourcePropertySource if exists
if (newResourcePropertySourceName.equals(resourcePropertySource.getName())) {
subEvents.add(replaced(this.context, newResourcePropertySource, resourcePropertySource));
addedSubEvent = true;
iterator.remove();
}
}
if (!addedSubEvent) {
subEvents.add(added(this.context, newResourcePropertySource));
}
// Add new ResourcePropertySource
resourcePropertySources.add(newResourcePropertySource);
}
}
private void updateResourcePropertySources(ResourcePropertySource newResourcePropertySource,
List resourcePropertySources, List subEvents) {
String newResourcePropertySourceName = newResourcePropertySource.getName();
Iterator iterator = resourcePropertySources.iterator();
List oldResourcePropertySources = new LinkedList<>();
while (iterator.hasNext()) {
ResourcePropertySource resourcePropertySource = iterator.next();
// Remove the old ResourcePropertySource if exists
if (newResourcePropertySourceName.equals(resourcePropertySource.getName())) {
oldResourcePropertySources.add(resourcePropertySource);
iterator.remove();
}
}
// Add new ResourcePropertySource
resourcePropertySources.add(newResourcePropertySource);
}
private List getResourcePropertySources(CompositePropertySource compositePropertySource) {
Collection> propertySources = compositePropertySource.getPropertySources();
List resourcePropertySources = new ArrayList<>(propertySources.size());
propertySources.stream()
.map(ResourcePropertySource.class::cast)
.forEach(resourcePropertySources::add);
return resourcePropertySources;
}
private CompositePropertySource getPropertySource(String propertySourceName) {
MutablePropertySources propertySources = getPropertySources();
PropertySource propertySource = propertySources.get(propertySourceName);
if (propertySource instanceof CompositePropertySource) {
return (CompositePropertySource) propertySource;
} else {
logger.warn("The CompositePropertySource can't be found by the name : {} , actual : {}", propertySourceName, propertySource);
}
return null;
}
private List resolvePropertySourceResources(EA extensionAttributes, String propertySourceName,
Comparator resourceComparator) throws Throwable {
String[] resourceValues = extensionAttributes.getValue();
boolean ignoreResourceNotFound = extensionAttributes.isIgnoreResourceNotFound();
if (ObjectUtils.isEmpty(resourceValues)) {
if (ignoreResourceNotFound) {
return emptyList();
}
throw new IllegalArgumentException("The 'value' attribute must be present at the annotation : @" + getAnnotationType().getName());
}
List propertySourceResources = new LinkedList<>();
for (String resourceValue : resourceValues) {
propertySourceResources.addAll(resolvePropertySourceResources(extensionAttributes, propertySourceName, resourceValue, resourceComparator));
}
sort(propertySourceResources);
return propertySourceResources;
}
protected List resolvePropertySourceResources(EA extensionAttributes, String propertySourceName,
String resourceValue, Comparator resourceComparator) throws Throwable {
boolean ignoreResourceNotFound = extensionAttributes.isIgnoreResourceNotFound();
Resource[] resources = null;
try {
resources = resolveResources(extensionAttributes, propertySourceName, resourceValue);
} catch (Throwable e) {
if (!ignoreResourceNotFound) {
throw e;
}
}
if (resources == null) {
return emptyList();
}
// iterate
int length = resources.length;
List propertySourceResources = new ArrayList<>(length);
for (int i = 0; i < length; i++) {
Resource resource = resources[i];
PropertySourceResource propertySourceResource = createPropertySourceResource(resourceValue, resource, resourceComparator);
propertySourceResources.add(propertySourceResource);
}
return propertySourceResources;
}
private MutablePropertySources getPropertySources() {
return this.getEnvironment().getPropertySources();
}
/**
* Creates an instance of {@link PropertySourceFactory}
*
* @param extensionAttributes the {@link PropertySourceExtensionAttributes annotation attributes} of {@link PropertySourceExtension}
* @return
* @see PropertySourceExtension#factory()
* @see PropertySourceFactory
*/
@NonNull
protected PropertySourceFactory createPropertySourceFactory(EA extensionAttributes) {
return createInstance(extensionAttributes, PropertySourceExtensionAttributes::getPropertySourceFactoryClass);
}
/**
* Creates an instance of {@link Comparator} for {@link Resource}
*
* @param extensionAttributes the {@link PropertySourceExtensionAttributes annotation attributes} of {@link PropertySourceExtension}
* @param propertySourceName the name of {PropertySource} declared by {@link PropertySourceExtension#name()}
* @return an instance of {@link Comparator} for {@link Resource}
* @see PropertySourceExtension#resourceComparator()
* @see Comparator
*/
@NonNull
protected Comparator createResourceComparator(EA extensionAttributes, String propertySourceName) {
return createInstance(extensionAttributes, PropertySourceExtensionAttributes::getResourceComparatorClass);
}
private PropertySourceResource createPropertySourceResource(String resourceValue, Resource resource, Comparator resourceComparator) {
return new PropertySourceResource(resourceValue, resource, resourceComparator);
}
protected String createResourcePropertySourceName(String propertySourceName, String resourceValue, Resource resource) {
Object suffix = resource.toString() == null ? resource.hashCode() : resource.toString();
return propertySourceName + "#" + resourceValue + "@" + suffix;
}
/**
* Create an instance of {@link ResourcePropertySource} for the specified {@link Resource resource}
*
* @param extensionAttributes the {@link PropertySourceExtensionAttributes annotation attributes} of {@link PropertySourceExtension}
* @param propertySourceName the name of {PropertySource} declared by {@link PropertySourceExtension#name()}
* @param factory the factory to the {@link PropertySource PropertySource} declared by {@link PropertySourceExtension#factory()}
* @param propertySourceResource the source of {@link PropertySource}
* @return an instance of {@link ResourcePropertySource}
* @throws Throwable
*/
protected final ResourcePropertySource createResourcePropertySource(EA extensionAttributes,
String propertySourceName,
PropertySourceFactory factory,
PropertySourceResource propertySourceResource) throws Throwable {
String resourceValue = propertySourceResource.resourceValue;
Resource resource = propertySourceResource.resource;
String encoding = extensionAttributes.getEncoding();
EncodedResource encodedResource = new EncodedResource(resource, encoding);
String name = createResourcePropertySourceName(propertySourceName, resourceValue, resource);
PropertySource propertySource = factory.createPropertySource(name, encodedResource);
return new ResourcePropertySource(propertySourceResource, propertySource);
}
/**
* Configure the {@link ResourcePropertySourcesRefresher} of {@link PropertySource} {@link Resource Resources} when
* {@link PropertySourceExtension#autoRefreshed()} is true
*
* @param extensionAttributes the {@link PropertySourceExtensionAttributes annotation attributes} of {@link PropertySourceExtension}
* @param propertySourceResources The sorted list of the resolved {@link Resource resources}
* @param propertySource The {@link CompositePropertySource property source} of current loader to be added into
* the Spring's {@link PropertySources property sources}
* @param refresher The Refresher of {@link PropertySource PropertySources'} {@link Resource}
* @throws Throwable any error
*/
protected void configureResourcePropertySourcesRefresher(EA extensionAttributes, List propertySourceResources,
CompositePropertySource propertySource,
ResourcePropertySourcesRefresher refresher) throws Throwable {
// DO NOTHING
}
/**
* Resolve the given resource value(s) to be {@link PropertySourceResource} array.
*
* @param extensionAttributes {@link EA}
* @param propertySourceName {@link PropertySourceExtension#name()}
* @param resourceValue the resource value to resolve
* @return nullable
* @throws Throwable
*/
@Nullable
protected abstract Resource[] resolveResources(EA extensionAttributes, String propertySourceName, String resourceValue) throws Throwable;
protected T createInstance(EA extensionAttributes, Function> classFunction) {
Class type = classFunction.apply(extensionAttributes);
return BeanUtils.instantiateClass(type);
}
@Override
public void setApplicationContext(ApplicationContext context) {
this.context = context;
}
/**
* The resource of {@link PropertySource}
*/
protected static class PropertySourceResource implements Comparable {
private final String resourceValue;
private final Resource resource;
private final Comparator resourceComparator;
public PropertySourceResource(String resourceValue, Resource resource, Comparator resourceComparator) {
this.resourceValue = resourceValue;
this.resource = resource;
this.resourceComparator = resourceComparator;
}
public String getResourceValue() {
return resourceValue;
}
public Resource getResource() {
return resource;
}
@Override
public int compareTo(PropertySourceResource o) {
return this.resourceComparator.compare(this.resource, o.resource);
}
}
/**
* The {@link Resource}-oriented {@link PropertySource}
*/
private static class ResourcePropertySource extends EnumerablePropertySource implements Comparable> {
private final PropertySourceResource propertySourceResource;
private final PropertySource original;
private final EnumerablePropertySource enumerablePropertySource;
public ResourcePropertySource(PropertySourceResource propertySourceResource, PropertySource original) {
super(original.getName(), original.getSource());
this.propertySourceResource = propertySourceResource;
this.original = original;
this.enumerablePropertySource = original instanceof EnumerablePropertySource ? (EnumerablePropertySource) original : null;
}
@Override
public String[] getPropertyNames() {
if (enumerablePropertySource == null) {
return EMPTY_STRING_ARRAY;
}
return enumerablePropertySource.getPropertyNames();
}
@Override
public Object getProperty(String name) {
if (enumerablePropertySource == null) {
return null;
}
return enumerablePropertySource.getProperty(name);
}
/**
* @return the value of resource
*/
public String getResourceValue() {
return propertySourceResource.resourceValue;
}
public Resource getResource() {
return propertySourceResource.resource;
}
/**
* @return the original {@link PropertySource}
*/
public PropertySource getOriginal() {
return original;
}
@Override
public int compareTo(ResourcePropertySource o) {
return this.propertySourceResource.compareTo(o.propertySourceResource);
}
}
/**
* The Refresher of {@link PropertySources PropertySources'} for {@link Resource}
*
* @author Mercy
* @see PropertySource
* @see Resource
* @since 1.0.0
*/
@FunctionalInterface
protected interface ResourcePropertySourcesRefresher {
/**
* Refresh the {@link PropertySources PropertySources} on {@link Resource} being refreshed
*
* @param resourceValue the value of resource declared by {@link PropertySourceExtension#value()}
* @param resource the optional {@link PropertySource PropertySources'} {@link Resource}.
* If resource
is null
, it indicates the resource is not specified,
* the actual resource(s) will be resolved by the resourceValue
, or refreshes
* the {@link PropertySources PropertySources} from the specified {@link Resource}
* @throws Throwable any error occurs
*/
void refresh(String resourceValue, @Nullable Resource resource) throws Throwable;
/**
* Refresh the {@link PropertySources PropertySources} on the {@link Resource}(s) that was or were resolved by the value
*
* @param resourceValue the value of resource declared by {@link PropertySourceExtension#value()}
* @throws Throwable any error occurs
*/
default void refresh(String resourceValue) throws Throwable {
refresh(resourceValue, null);
}
}
}