diff --git a/core/pom.xml b/core/pom.xml index 9e25ad28f..5b0813aef 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -134,6 +134,7 @@ com.taobao.middleware logger.api + log4j log4j @@ -161,6 +162,12 @@ junit test + + org.assertj + assertj-core + test + + org.benf cfr diff --git a/core/src/main/java/com/taobao/arthas/core/env/AbstractPropertyResolver.java b/core/src/main/java/com/taobao/arthas/core/env/AbstractPropertyResolver.java new file mode 100644 index 000000000..9cc715aa8 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/env/AbstractPropertyResolver.java @@ -0,0 +1,226 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed 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 + * + * https://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 com.taobao.arthas.core.env; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Abstract base class for resolving properties against any underlying source. + * + * @author Chris Beams + * @author Juergen Hoeller + * @since 3.1 + */ +public abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver { + + protected ConfigurableConversionService conversionService = new DefaultConversionService(); + + private PropertyPlaceholderHelper nonStrictHelper; + + private PropertyPlaceholderHelper strictHelper; + + private boolean ignoreUnresolvableNestedPlaceholders = false; + + private String placeholderPrefix = SystemPropertyUtils.PLACEHOLDER_PREFIX; + + private String placeholderSuffix = SystemPropertyUtils.PLACEHOLDER_SUFFIX; + + private String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR; + + private final Set requiredProperties = new LinkedHashSet(); + + public ConfigurableConversionService getConversionService() { + return this.conversionService; + } + + public void setConversionService(ConfigurableConversionService conversionService) { + this.conversionService = conversionService; + } + + /** + * Set the prefix that placeholders replaced by this resolver must begin with. + *

+ * The default is "${". + * + * @see org.springframework.util.SystemPropertyUtils#PLACEHOLDER_PREFIX + */ + @Override + public void setPlaceholderPrefix(String placeholderPrefix) { + this.placeholderPrefix = placeholderPrefix; + } + + /** + * Set the suffix that placeholders replaced by this resolver must end with. + *

+ * The default is "}". + * + * @see org.springframework.util.SystemPropertyUtils#PLACEHOLDER_SUFFIX + */ + @Override + public void setPlaceholderSuffix(String placeholderSuffix) { + this.placeholderSuffix = placeholderSuffix; + } + + /** + * Specify the separating character between the placeholders replaced by this + * resolver and their associated default value, or {@code null} if no such + * special character should be processed as a value separator. + *

+ * The default is ":". + * + * @see org.springframework.util.SystemPropertyUtils#VALUE_SEPARATOR + */ + @Override + public void setValueSeparator(String valueSeparator) { + this.valueSeparator = valueSeparator; + } + + /** + * Set whether to throw an exception when encountering an unresolvable + * placeholder nested within the value of a given property. A {@code false} + * value indicates strict resolution, i.e. that an exception will be thrown. A + * {@code true} value indicates that unresolvable nested placeholders should be + * passed through in their unresolved ${...} form. + *

+ * The default is {@code false}. + * + * @since 3.2 + */ + @Override + public void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders) { + this.ignoreUnresolvableNestedPlaceholders = ignoreUnresolvableNestedPlaceholders; + } + + @Override + public void setRequiredProperties(String... requiredProperties) { + for (String key : requiredProperties) { + this.requiredProperties.add(key); + } + } + + @Override + public void validateRequiredProperties() { + MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException(); + for (String key : this.requiredProperties) { + if (this.getProperty(key) == null) { + ex.addMissingRequiredProperty(key); + } + } + if (!ex.getMissingRequiredProperties().isEmpty()) { + throw ex; + } + } + + @Override + public boolean containsProperty(String key) { + return (getProperty(key) != null); + } + + @Override + public String getProperty(String key) { + return getProperty(key, String.class); + } + + @Override + public String getProperty(String key, String defaultValue) { + String value = getProperty(key); + return (value != null ? value : defaultValue); + } + + @Override + public T getProperty(String key, Class targetType, T defaultValue) { + T value = getProperty(key, targetType); + return (value != null ? value : defaultValue); + } + + @Override + public String getRequiredProperty(String key) throws IllegalStateException { + String value = getProperty(key); + if (value == null) { + throw new IllegalStateException("Required key '" + key + "' not found"); + } + return value; + } + + @Override + public T getRequiredProperty(String key, Class valueType) throws IllegalStateException { + T value = getProperty(key, valueType); + if (value == null) { + throw new IllegalStateException("Required key '" + key + "' not found"); + } + return value; + } + + @Override + public String resolvePlaceholders(String text) { + if (this.nonStrictHelper == null) { + this.nonStrictHelper = createPlaceholderHelper(true); + } + return doResolvePlaceholders(text, this.nonStrictHelper); + } + + @Override + public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException { + if (this.strictHelper == null) { + this.strictHelper = createPlaceholderHelper(false); + } + return doResolvePlaceholders(text, this.strictHelper); + } + + /** + * Resolve placeholders within the given string, deferring to the value of + * {@link #setIgnoreUnresolvableNestedPlaceholders} to determine whether any + * unresolvable placeholders should raise an exception or be ignored. + *

+ * Invoked from {@link #getProperty} and its variants, implicitly resolving + * nested placeholders. In contrast, {@link #resolvePlaceholders} and + * {@link #resolveRequiredPlaceholders} do not delegate to this method + * but rather perform their own handling of unresolvable placeholders, as + * specified by each of those methods. + * + * @since 3.2 + * @see #setIgnoreUnresolvableNestedPlaceholders + */ + protected String resolveNestedPlaceholders(String value) { + return (this.ignoreUnresolvableNestedPlaceholders ? resolvePlaceholders(value) + : resolveRequiredPlaceholders(value)); + } + + private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) { + return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix, this.valueSeparator, + ignoreUnresolvablePlaceholders); + } + + private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) { + return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() { + public String resolvePlaceholder(String placeholderName) { + return getPropertyAsRawString(placeholderName); + } + }); + } + + /** + * Retrieve the specified property as a raw String, i.e. without resolution of + * nested placeholders. + * + * @param key the property name to resolve + * @return the property value or {@code null} if none found + */ + protected abstract String getPropertyAsRawString(String key); + +} diff --git a/core/src/main/java/com/taobao/arthas/core/env/ArthasEnvironment.java b/core/src/main/java/com/taobao/arthas/core/env/ArthasEnvironment.java new file mode 100644 index 000000000..d7bfccdf5 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/env/ArthasEnvironment.java @@ -0,0 +1,122 @@ +package com.taobao.arthas.core.env; + +import java.security.AccessControlException; +import java.util.Map; + +/** + * + * @author hengyunabc 2019-12-27 + * + */ +public class ArthasEnvironment implements Environment { + /** System environment property source name: {@value}. */ + public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment"; + + /** JVM system properties property source name: {@value}. */ + public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties"; + + private final MutablePropertySources propertySources = new MutablePropertySources(); + + private final ConfigurablePropertyResolver propertyResolver = new PropertySourcesPropertyResolver( + this.propertySources); + + public ArthasEnvironment() { + propertySources + .addLast(new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties())); + propertySources.addLast( + new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment())); + } + + /** + * Add the given property source object with lowest precedence. + */ + public void addLast(PropertySource propertySource) { + this.propertySources.addLast(propertySource); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public Map getSystemProperties() { + try { + return (Map) System.getProperties(); + } catch (AccessControlException ex) { + return (Map) new ReadOnlySystemAttributesMap() { + @Override + protected String getSystemAttribute(String attributeName) { + try { + return System.getProperty(attributeName); + } catch (AccessControlException ex) { + return null; + } + } + }; + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public Map getSystemEnvironment() { + try { + return (Map) System.getenv(); + } catch (AccessControlException ex) { + return (Map) new ReadOnlySystemAttributesMap() { + @Override + protected String getSystemAttribute(String attributeName) { + try { + return System.getenv(attributeName); + } catch (AccessControlException ex) { + return null; + } + } + }; + } + } + + // --------------------------------------------------------------------- + // Implementation of PropertyResolver interface + // --------------------------------------------------------------------- + + @Override + public boolean containsProperty(String key) { + return this.propertyResolver.containsProperty(key); + } + + @Override + public String getProperty(String key) { + return this.propertyResolver.getProperty(key); + } + + @Override + public String getProperty(String key, String defaultValue) { + return this.propertyResolver.getProperty(key, defaultValue); + } + + @Override + public T getProperty(String key, Class targetType) { + return this.propertyResolver.getProperty(key, targetType); + } + + @Override + public T getProperty(String key, Class targetType, T defaultValue) { + return this.propertyResolver.getProperty(key, targetType, defaultValue); + } + + @Override + public String getRequiredProperty(String key) throws IllegalStateException { + return this.propertyResolver.getRequiredProperty(key); + } + + @Override + public T getRequiredProperty(String key, Class targetType) throws IllegalStateException { + return this.propertyResolver.getRequiredProperty(key, targetType); + } + + @Override + public String resolvePlaceholders(String text) { + return this.propertyResolver.resolvePlaceholders(text); + } + + @Override + public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException { + return this.propertyResolver.resolveRequiredPlaceholders(text); + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/env/ConfigurableConversionService.java b/core/src/main/java/com/taobao/arthas/core/env/ConfigurableConversionService.java new file mode 100644 index 000000000..28ff54841 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/env/ConfigurableConversionService.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * Licensed 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 + * + * https://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 com.taobao.arthas.core.env; + +/** + * Configuration interface to be implemented by most if not all + * {@link ConversionService} types. Consolidates the read-only operations + * exposed by {@link ConversionService} and the mutating operations of + * {@link ConverterRegistry} to allow for convenient ad-hoc addition and removal + * of {@link org.springframework.core.convert.converter.Converter Converters} + * through. The latter is particularly useful when working against a + * {@link org.springframework.core.env.ConfigurableEnvironment + * ConfigurableEnvironment} instance in application context bootstrapping code. + * + * @author Chris Beams + * @since 3.1 + * @see org.springframework.core.env.ConfigurablePropertyResolver#getConversionService() + * @see org.springframework.core.env.ConfigurableEnvironment + * @see org.springframework.context.ConfigurableApplicationContext#getEnvironment() + */ +public interface ConfigurableConversionService extends ConversionService { + +} diff --git a/core/src/main/java/com/taobao/arthas/core/env/ConfigurablePropertyResolver.java b/core/src/main/java/com/taobao/arthas/core/env/ConfigurablePropertyResolver.java new file mode 100644 index 000000000..c0f63ae68 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/env/ConfigurablePropertyResolver.java @@ -0,0 +1,111 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed 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 + * + * https://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 com.taobao.arthas.core.env; + +/** + * Configuration interface to be implemented by most if not all + * {@link PropertyResolver} types. Provides facilities for accessing and + * customizing the {@link org.springframework.core.convert.ConversionService + * ConversionService} used when converting property values from one type to + * another. + * + * @author Chris Beams + * @since 3.1 + */ +public interface ConfigurablePropertyResolver extends PropertyResolver { + + /** + * Return the {@link ConfigurableConversionService} used when performing type + * conversions on properties. + *

+ * The configurable nature of the returned conversion service allows for the + * convenient addition and removal of individual {@code Converter} instances: + * + *

+     * ConfigurableConversionService cs = env.getConversionService();
+     * cs.addConverter(new FooConverter());
+     * 
+ * + * @see PropertyResolver#getProperty(String, Class) + * @see org.springframework.core.convert.converter.ConverterRegistry#addConverter + */ + ConfigurableConversionService getConversionService(); + + /** + * Set the {@link ConfigurableConversionService} to be used when performing type + * conversions on properties. + *

+ * Note: as an alternative to fully replacing the + * {@code ConversionService}, consider adding or removing individual + * {@code Converter} instances by drilling into {@link #getConversionService()} + * and calling methods such as {@code #addConverter}. + * + * @see PropertyResolver#getProperty(String, Class) + * @see #getConversionService() + * @see org.springframework.core.convert.converter.ConverterRegistry#addConverter + */ + void setConversionService(ConfigurableConversionService conversionService); + + /** + * Set the prefix that placeholders replaced by this resolver must begin with. + */ + void setPlaceholderPrefix(String placeholderPrefix); + + /** + * Set the suffix that placeholders replaced by this resolver must end with. + */ + void setPlaceholderSuffix(String placeholderSuffix); + + /** + * Specify the separating character between the placeholders replaced by this + * resolver and their associated default value, or {@code null} if no such + * special character should be processed as a value separator. + */ + void setValueSeparator(String valueSeparator); + + /** + * Set whether to throw an exception when encountering an unresolvable + * placeholder nested within the value of a given property. A {@code false} + * value indicates strict resolution, i.e. that an exception will be thrown. A + * {@code true} value indicates that unresolvable nested placeholders should be + * passed through in their unresolved ${...} form. + *

+ * Implementations of {@link #getProperty(String)} and its variants must inspect + * the value set here to determine correct behavior when property values contain + * unresolvable placeholders. + * + * @since 3.2 + */ + void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders); + + /** + * Specify which properties must be present, to be verified by + * {@link #validateRequiredProperties()}. + */ + void setRequiredProperties(String... requiredProperties); + + /** + * Validate that each of the properties specified by + * {@link #setRequiredProperties} is present and resolves to a non-{@code null} + * value. + * + * @throws MissingRequiredPropertiesException if any of the required properties + * are not resolvable. + */ + void validateRequiredProperties() throws MissingRequiredPropertiesException; + +} diff --git a/core/src/main/java/com/taobao/arthas/core/env/ConversionService.java b/core/src/main/java/com/taobao/arthas/core/env/ConversionService.java new file mode 100644 index 000000000..4e26baf51 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/env/ConversionService.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed 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 com.taobao.arthas.core.env; + +/** + * A service interface for type conversion. This is the entry point into the + * convert system. Call {@link #convert(Object, Class)} to perform a thread-safe + * type conversion using this system. + * + * @author Keith Donald + * @author Phillip Webb + * @since 3.0 + */ +public interface ConversionService { + + /** + * Return {@code true} if objects of {@code sourceType} can be converted to the + * {@code targetType}. + *

+ * If this method returns {@code true}, it means {@link #convert(Object, Class)} + * is capable of converting an instance of {@code sourceType} to + * {@code targetType}. + *

+ * Special note on collections, arrays, and maps types: For conversion between + * collection, array, and map types, this method will return {@code true} even + * though a convert invocation may still generate a {@link ConversionException} + * if the underlying elements are not convertible. Callers are expected to + * handle this exceptional case when working with collections and maps. + * + * @param sourceType the source type to convert from (may be {@code null} if + * source is {@code null}) + * @param targetType the target type to convert to (required) + * @return {@code true} if a conversion can be performed, {@code false} if not + * @throws IllegalArgumentException if {@code targetType} is {@code null} + */ + boolean canConvert(Class sourceType, Class targetType); + + /** + * Convert the given {@code source} to the specified {@code targetType}. + * + * @param source the source object to convert (may be {@code null}) + * @param targetType the target type to convert to (required) + * @return the converted object, an instance of targetType + * @throws ConversionException if a conversion exception occurred + * @throws IllegalArgumentException if targetType is {@code null} + */ + T convert(Object source, Class targetType); + +} diff --git a/core/src/main/java/com/taobao/arthas/core/env/DefaultConversionService.java b/core/src/main/java/com/taobao/arthas/core/env/DefaultConversionService.java new file mode 100644 index 000000000..462ce4bd3 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/env/DefaultConversionService.java @@ -0,0 +1,17 @@ +package com.taobao.arthas.core.env; + +public class DefaultConversionService implements ConfigurableConversionService { + + @Override + public boolean canConvert(Class sourceType, Class targetType) { + // TODO Auto-generated method stub + return true; + } + + @Override + public T convert(Object source, Class targetType) { + // TODO Auto-generated method stub + return (T) source; + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/env/EnumerablePropertySource.java b/core/src/main/java/com/taobao/arthas/core/env/EnumerablePropertySource.java new file mode 100644 index 000000000..189c7c9d2 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/env/EnumerablePropertySource.java @@ -0,0 +1,85 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed 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 + * + * https://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 com.taobao.arthas.core.env; + +/** + * A {@link PropertySource} implementation capable of interrogating its + * underlying source object to enumerate all possible property name/value pairs. + * Exposes the {@link #getPropertyNames()} method to allow callers to introspect + * available properties without having to access the underlying source object. + * This also facilitates a more efficient implementation of + * {@link #containsProperty(String)}, in that it can call + * {@link #getPropertyNames()} and iterate through the returned array rather + * than attempting a call to {@link #getProperty(String)} which may be more + * expensive. Implementations may consider caching the result of + * {@link #getPropertyNames()} to fully exploit this performance opportunity. + * + *

+ * Most framework-provided {@code PropertySource} implementations are + * enumerable; a counter-example would be {@code JndiPropertySource} where, due + * to the nature of JNDI it is not possible to determine all possible property + * names at any given time; rather it is only possible to try to access a + * property (via {@link #getProperty(String)}) in order to evaluate whether it + * is present or not. + * + * @author Chris Beams + * @author Juergen Hoeller + * @since 3.1 + * @param the source type + */ +public abstract class EnumerablePropertySource extends PropertySource { + + public EnumerablePropertySource(String name, T source) { + super(name, source); + } + + protected EnumerablePropertySource(String name) { + super(name); + } + + /** + * Return whether this {@code PropertySource} contains a property with the given + * name. + *

+ * This implementation checks for the presence of the given name within the + * {@link #getPropertyNames()} array. + * + * @param name the name of the property to find + */ + @Override + public boolean containsProperty(String name) { + String[] propertyNames = getPropertyNames(); + if (propertyNames == null) { + return false; + } + for (String temp : propertyNames) { + if (temp.equals(name)) { + + return true; + } + } + return false; + + } + + /** + * Return the names of all properties contained by the {@linkplain #getSource() + * source} object (never {@code null}). + */ + public abstract String[] getPropertyNames(); + +} diff --git a/core/src/main/java/com/taobao/arthas/core/env/Environment.java b/core/src/main/java/com/taobao/arthas/core/env/Environment.java new file mode 100644 index 000000000..c6a8de970 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/env/Environment.java @@ -0,0 +1,5 @@ +package com.taobao.arthas.core.env; + +public interface Environment extends PropertyResolver { + +} diff --git a/core/src/main/java/com/taobao/arthas/core/env/MapPropertySource.java b/core/src/main/java/com/taobao/arthas/core/env/MapPropertySource.java new file mode 100644 index 000000000..825a11624 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/env/MapPropertySource.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed 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 + * + * https://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 com.taobao.arthas.core.env; + +import java.util.Map; + +import org.apache.logging.log4j.util.PropertiesPropertySource; + +/** + * {@link PropertySource} that reads keys and values from a {@code Map} object. + * + * @author Chris Beams + * @author Juergen Hoeller + * @since 3.1 + * @see PropertiesPropertySource + */ +public class MapPropertySource extends EnumerablePropertySource> { + + public MapPropertySource(String name, Map source) { + super(name, source); + } + + @Override + public Object getProperty(String name) { + return this.source.get(name); + } + + @Override + public boolean containsProperty(String name) { + return this.source.containsKey(name); + } + + @Override + public String[] getPropertyNames() { + return this.source.keySet().toArray(new String[0]); + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/env/MissingRequiredPropertiesException.java b/core/src/main/java/com/taobao/arthas/core/env/MissingRequiredPropertiesException.java new file mode 100644 index 000000000..bda4a596c --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/env/MissingRequiredPropertiesException.java @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed 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 + * + * https://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 com.taobao.arthas.core.env; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Exception thrown when required properties are not found. + * + * @author Chris Beams + * @since 3.1 + * @see ConfigurablePropertyResolver#setRequiredProperties(String...) + * @see ConfigurablePropertyResolver#validateRequiredProperties() + * @see org.springframework.context.support.AbstractApplicationContext#prepareRefresh() + */ +@SuppressWarnings("serial") +public class MissingRequiredPropertiesException extends IllegalStateException { + + private final Set missingRequiredProperties = new LinkedHashSet(); + + void addMissingRequiredProperty(String key) { + this.missingRequiredProperties.add(key); + } + + @Override + public String getMessage() { + return "The following properties were declared as required but could not be resolved: " + + getMissingRequiredProperties(); + } + + /** + * Return the set of properties marked as required but not present upon + * validation. + * + * @see ConfigurablePropertyResolver#setRequiredProperties(String...) + * @see ConfigurablePropertyResolver#validateRequiredProperties() + */ + public Set getMissingRequiredProperties() { + return this.missingRequiredProperties; + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/env/MutablePropertySources.java b/core/src/main/java/com/taobao/arthas/core/env/MutablePropertySources.java new file mode 100644 index 000000000..909a70ee5 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/env/MutablePropertySources.java @@ -0,0 +1,306 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed 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 com.taobao.arthas.core.env; + +import java.util.Iterator; +import java.util.LinkedList; + +/** + * Default implementation of the {@link PropertySources} interface. Allows + * manipulation of contained property sources and provides a constructor for + * copying an existing {@code PropertySources} instance. + * + *

+ * Where precedence is mentioned in methods such as {@link #addFirst} + * and {@link #addLast}, this is with regard to the order in which property + * sources will be searched when resolving a given property with a + * {@link PropertyResolver}. + * + * @author Chris Beams + * @since 3.1 + * @see PropertySourcesPropertyResolver + */ +public class MutablePropertySources implements PropertySources { + + static final String NON_EXISTENT_PROPERTY_SOURCE_MESSAGE = "PropertySource named [%s] does not exist"; + static final String ILLEGAL_RELATIVE_ADDITION_MESSAGE = "PropertySource named [%s] cannot be added relative to itself"; + + private final LinkedList> propertySourceList = new LinkedList>(); + + /** + * Create a new {@link MutablePropertySources} object. + */ + public MutablePropertySources() { + } + + /** + * Create a new {@code MutablePropertySources} from the given propertySources + * object, preserving the original order of contained {@code PropertySource} + * objects. + */ + public MutablePropertySources(PropertySources propertySources) { + this(); + for (PropertySource propertySource : propertySources) { + this.addLast(propertySource); + } + } + + public boolean contains(String name) { + return this.propertySourceList.contains(PropertySource.named(name)); + } + + public PropertySource get(String name) { + int index = this.propertySourceList.indexOf(PropertySource.named(name)); + return index == -1 ? null : this.propertySourceList.get(index); + } + + public Iterator> iterator() { + return this.propertySourceList.iterator(); + } + + /** + * Add the given property source object with highest precedence. + */ + public void addFirst(PropertySource propertySource) { +// if (logger.isDebugEnabled()) { +// logger.debug(String.format("Adding [%s] PropertySource with highest search precedence", +// propertySource.getName())); +// } + removeIfPresent(propertySource); + this.propertySourceList.addFirst(propertySource); + } + + /** + * Add the given property source object with lowest precedence. + */ + public void addLast(PropertySource propertySource) { +// if (logger.isDebugEnabled()) { +// logger.debug(String.format("Adding [%s] PropertySource with lowest search precedence", +// propertySource.getName())); +// } + removeIfPresent(propertySource); + this.propertySourceList.addLast(propertySource); + } + + /** + * Add the given property source object with precedence immediately higher than + * the named relative property source. + */ + public void addBefore(String relativePropertySourceName, PropertySource propertySource) { +// if (logger.isDebugEnabled()) { +// logger.debug(String.format("Adding [%s] PropertySource with search precedence immediately higher than [%s]", +// propertySource.getName(), relativePropertySourceName)); +// } + assertLegalRelativeAddition(relativePropertySourceName, propertySource); + removeIfPresent(propertySource); + int index = assertPresentAndGetIndex(relativePropertySourceName); + addAtIndex(index, propertySource); + } + + /** + * Add the given property source object with precedence immediately lower than + * the named relative property source. + */ + public void addAfter(String relativePropertySourceName, PropertySource propertySource) { +// if (logger.isDebugEnabled()) { +// logger.debug(String.format("Adding [%s] PropertySource with search precedence immediately lower than [%s]", +// propertySource.getName(), relativePropertySourceName)); +// } + assertLegalRelativeAddition(relativePropertySourceName, propertySource); + removeIfPresent(propertySource); + int index = assertPresentAndGetIndex(relativePropertySourceName); + addAtIndex(index + 1, propertySource); + } + + /** + * Return the precedence of the given property source, {@code -1} if not found. + */ + public int precedenceOf(PropertySource propertySource) { + return this.propertySourceList.indexOf(propertySource); + } + + /** + * Remove and return the property source with the given name, {@code null} if + * not found. + * + * @param name the name of the property source to find and remove + */ + public PropertySource remove(String name) { +// if (logger.isDebugEnabled()) { +// logger.debug(String.format("Removing [%s] PropertySource", name)); +// } + int index = this.propertySourceList.indexOf(PropertySource.named(name)); + return index == -1 ? null : this.propertySourceList.remove(index); + } + + /** + * Replace the property source with the given name with the given property + * source object. + * + * @param name the name of the property source to find and replace + * @param propertySource the replacement property source + * @throws IllegalArgumentException if no property source with the given name is + * present + * @see #contains + */ + public void replace(String name, PropertySource propertySource) { +// if (logger.isDebugEnabled()) { +// logger.debug(String.format("Replacing [%s] PropertySource with [%s]", +// name, propertySource.getName())); +// } + int index = assertPresentAndGetIndex(name); + this.propertySourceList.set(index, propertySource); + } + + /** + * Return the number of {@link PropertySource} objects contained. + */ + public int size() { + return this.propertySourceList.size(); + } + + @Override + public String toString() { + String[] names = new String[this.size()]; + for (int i = 0; i < size(); i++) { + names[i] = this.propertySourceList.get(i).getName(); + } + return String.format("[%s]", arrayToCommaDelimitedString(names)); + } + + /** + * Ensure that the given property source is not being added relative to itself. + */ + protected void assertLegalRelativeAddition(String relativePropertySourceName, PropertySource propertySource) { +// String newPropertySourceName = propertySource.getName(); +// Assert.isTrue(!relativePropertySourceName.equals(newPropertySourceName), +// String.format(ILLEGAL_RELATIVE_ADDITION_MESSAGE, newPropertySourceName)); + } + + /** + * Remove the given property source if it is present. + */ + protected void removeIfPresent(PropertySource propertySource) { + if (this.propertySourceList.contains(propertySource)) { + this.propertySourceList.remove(propertySource); + } + } + + /** + * Add the given property source at a particular index in the list. + */ + private void addAtIndex(int index, PropertySource propertySource) { + removeIfPresent(propertySource); + this.propertySourceList.add(index, propertySource); + } + + /** + * Assert that the named property source is present and return its index. + * + * @param name the {@linkplain PropertySource#getName() name of the property + * source} to find + * @throws IllegalArgumentException if the named property source is not present + */ + private int assertPresentAndGetIndex(String name) { + int index = this.propertySourceList.indexOf(PropertySource.named(name)); +// Assert.isTrue(index >= 0, String.format(NON_EXISTENT_PROPERTY_SOURCE_MESSAGE, name)); + return index; + } + + /** + * Convenience method to return a String array as a delimited (e.g. CSV) String. + * E.g. useful for {@code toString()} implementations. + * + * @param arr the array to display + * @param delim the delimiter to use (probably a ",") + * @return the delimited String + */ + private static String arrayToDelimitedString(Object[] arr, String delim) { + if (arr == null || arr.length == 0) { + return ""; + } + if (arr.length == 1) { + return nullSafeToString(arr[0]); + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < arr.length; i++) { + if (i > 0) { + sb.append(delim); + } + sb.append(arr[i]); + } + return sb.toString(); + } + + /** + * Return a String representation of the specified Object. + *

+ * Builds a String representation of the contents in case of an array. Returns + * {@code "null"} if {@code obj} is {@code null}. + * + * @param obj the object to build a String representation for + * @return a String representation of {@code obj} + */ + private static String nullSafeToString(Object obj) { + if (obj == null) { + return "null"; + } + if (obj instanceof String) { + return (String) obj; + } + if (obj instanceof Object[]) { + return nullSafeToString((Object[]) obj); + } + if (obj instanceof boolean[]) { + return nullSafeToString((boolean[]) obj); + } + if (obj instanceof byte[]) { + return nullSafeToString((byte[]) obj); + } + if (obj instanceof char[]) { + return nullSafeToString((char[]) obj); + } + if (obj instanceof double[]) { + return nullSafeToString((double[]) obj); + } + if (obj instanceof float[]) { + return nullSafeToString((float[]) obj); + } + if (obj instanceof int[]) { + return nullSafeToString((int[]) obj); + } + if (obj instanceof long[]) { + return nullSafeToString((long[]) obj); + } + if (obj instanceof short[]) { + return nullSafeToString((short[]) obj); + } + String str = obj.toString(); + return (str != null ? str : ""); + } + + /** + * Convenience method to return a String array as a CSV String. E.g. useful for + * {@code toString()} implementations. + * + * @param arr the array to display + * @return the delimited String + */ + private static String arrayToCommaDelimitedString(Object[] arr) { + return arrayToDelimitedString(arr, ","); + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/env/PropertiesPropertySource.java b/core/src/main/java/com/taobao/arthas/core/env/PropertiesPropertySource.java new file mode 100644 index 000000000..fd9a750a1 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/env/PropertiesPropertySource.java @@ -0,0 +1,56 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed 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 + * + * https://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 com.taobao.arthas.core.env; + +import java.util.Map; +import java.util.Properties; + +/** + * {@link PropertySource} implementation that extracts properties from a + * {@link java.util.Properties} object. + * + *

+ * Note that because a {@code Properties} object is technically an + * {@code } {@link java.util.Hashtable Hashtable}, one may + * contain non-{@code String} keys or values. This implementation, however is + * restricted to accessing only {@code String}-based keys and values, in the + * same fashion as {@link Properties#getProperty} and + * {@link Properties#setProperty}. + * + * @author Chris Beams + * @author Juergen Hoeller + * @since 3.1 + */ +public class PropertiesPropertySource extends MapPropertySource { + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public PropertiesPropertySource(String name, Properties source) { + super(name, (Map) source); + } + + protected PropertiesPropertySource(String name, Map source) { + super(name, source); + } + + @Override + public String[] getPropertyNames() { + synchronized (this.source) { + return super.getPropertyNames(); + } + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/env/PropertyPlaceholderHelper.java b/core/src/main/java/com/taobao/arthas/core/env/PropertyPlaceholderHelper.java new file mode 100644 index 000000000..b76eadc17 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/env/PropertyPlaceholderHelper.java @@ -0,0 +1,244 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed 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 + * + * https://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 com.taobao.arthas.core.env; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +/** + * Utility class for working with Strings that have placeholder values in them. + * A placeholder takes the form {@code ${name}}. Using + * {@code PropertyPlaceholderHelper} these placeholders can be substituted for + * user-supplied values. + *

+ * Values for substitution can be supplied using a {@link Properties} instance + * or using a {@link PlaceholderResolver}. + * + * @author Juergen Hoeller + * @author Rob Harrop + * @since 3.0 + */ +public class PropertyPlaceholderHelper { + + private static final Map wellKnownSimplePrefixes = new HashMap(4); + + static { + wellKnownSimplePrefixes.put("}", "{"); + wellKnownSimplePrefixes.put("]", "["); + wellKnownSimplePrefixes.put(")", "("); + } + + private final String placeholderPrefix; + + private final String placeholderSuffix; + + private final String simplePrefix; + + private final String valueSeparator; + + private final boolean ignoreUnresolvablePlaceholders; + + /** + * Creates a new {@code PropertyPlaceholderHelper} that uses the supplied prefix + * and suffix. Unresolvable placeholders are ignored. + * + * @param placeholderPrefix the prefix that denotes the start of a placeholder + * @param placeholderSuffix the suffix that denotes the end of a placeholder + */ + public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix) { + this(placeholderPrefix, placeholderSuffix, null, true); + } + + /** + * Creates a new {@code PropertyPlaceholderHelper} that uses the supplied prefix + * and suffix. + * + * @param placeholderPrefix the prefix that denotes the start of a + * placeholder + * @param placeholderSuffix the suffix that denotes the end of a + * placeholder + * @param valueSeparator the separating character between the + * placeholder variable and the associated + * default value, if any + * @param ignoreUnresolvablePlaceholders indicates whether unresolvable + * placeholders should be ignored + * ({@code true}) or cause an exception + * ({@code false}) + */ + public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix, String valueSeparator, + boolean ignoreUnresolvablePlaceholders) { + + this.placeholderPrefix = placeholderPrefix; + this.placeholderSuffix = placeholderSuffix; + String simplePrefixForSuffix = wellKnownSimplePrefixes.get(this.placeholderSuffix); + if (simplePrefixForSuffix != null && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) { + this.simplePrefix = simplePrefixForSuffix; + } else { + this.simplePrefix = this.placeholderPrefix; + } + this.valueSeparator = valueSeparator; + this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders; + } + + /** + * Replaces all placeholders of format {@code ${name}} with the corresponding + * property from the supplied {@link Properties}. + * + * @param value the value containing the placeholders to be replaced + * @param properties the {@code Properties} to use for replacement + * @return the supplied value with placeholders replaced inline + */ + public String replacePlaceholders(String value, final Properties properties) { + return replacePlaceholders(value, new PlaceholderResolver() { + public String resolvePlaceholder(String placeholderName) { + return properties.getProperty(placeholderName); + } + }); + } + + /** + * Replaces all placeholders of format {@code ${name}} with the value returned + * from the supplied {@link PlaceholderResolver}. + * + * @param value the value containing the placeholders to be + * replaced + * @param placeholderResolver the {@code PlaceholderResolver} to use for + * replacement + * @return the supplied value with placeholders replaced inline + */ + public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) { + return parseStringValue(value, placeholderResolver, null); + } + + protected String parseStringValue(String value, PlaceholderResolver placeholderResolver, + Set visitedPlaceholders) { + + int startIndex = value.indexOf(this.placeholderPrefix); + if (startIndex == -1) { + return value; + } + + StringBuilder result = new StringBuilder(value); + while (startIndex != -1) { + int endIndex = findPlaceholderEndIndex(result, startIndex); + if (endIndex != -1) { + String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex); + String originalPlaceholder = placeholder; + if (visitedPlaceholders == null) { + visitedPlaceholders = new HashSet(4); + } + if (!visitedPlaceholders.add(originalPlaceholder)) { + throw new IllegalArgumentException( + "Circular placeholder reference '" + originalPlaceholder + "' in property definitions"); + } + // Recursive invocation, parsing placeholders contained in the placeholder key. + placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders); + // Now obtain the value for the fully resolved key... + String propVal = placeholderResolver.resolvePlaceholder(placeholder); + if (propVal == null && this.valueSeparator != null) { + int separatorIndex = placeholder.indexOf(this.valueSeparator); + if (separatorIndex != -1) { + String actualPlaceholder = placeholder.substring(0, separatorIndex); + String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length()); + propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder); + if (propVal == null) { + propVal = defaultValue; + } + } + } + if (propVal != null) { + // Recursive invocation, parsing placeholders contained in the + // previously resolved placeholder value. + propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders); + result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal); + startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length()); + } else if (this.ignoreUnresolvablePlaceholders) { + // Proceed with unprocessed value. + startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length()); + } else { + throw new IllegalArgumentException( + "Could not resolve placeholder '" + placeholder + "'" + " in value \"" + value + "\""); + } + visitedPlaceholders.remove(originalPlaceholder); + } else { + startIndex = -1; + } + } + return result.toString(); + } + + private int findPlaceholderEndIndex(CharSequence buf, int startIndex) { + int index = startIndex + this.placeholderPrefix.length(); + int withinNestedPlaceholder = 0; + while (index < buf.length()) { + if (substringMatch(buf, index, this.placeholderSuffix)) { + if (withinNestedPlaceholder > 0) { + withinNestedPlaceholder--; + index = index + this.placeholderSuffix.length(); + } else { + return index; + } + } else if (substringMatch(buf, index, this.simplePrefix)) { + withinNestedPlaceholder++; + index = index + this.simplePrefix.length(); + } else { + index++; + } + } + return -1; + } + + /** + * Test whether the given string matches the given substring at the given index. + * + * @param str the original string (or StringBuilder) + * @param index the index in the original string to start matching against + * @param substring the substring to match at the given index + */ + public static boolean substringMatch(CharSequence str, int index, CharSequence substring) { + if (index + substring.length() > str.length()) { + return false; + } + for (int i = 0; i < substring.length(); i++) { + if (str.charAt(index + i) != substring.charAt(i)) { + return false; + } + } + return true; + } + + /** + * Strategy interface used to resolve replacement values for placeholders + * contained in Strings. + */ + @FunctionalInterface + public interface PlaceholderResolver { + + /** + * Resolve the supplied placeholder name to the replacement value. + * + * @param placeholderName the name of the placeholder to resolve + * @return the replacement value, or {@code null} if no replacement is to be + * made + */ + String resolvePlaceholder(String placeholderName); + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/env/PropertyResolver.java b/core/src/main/java/com/taobao/arthas/core/env/PropertyResolver.java new file mode 100644 index 000000000..fdcbab79c --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/env/PropertyResolver.java @@ -0,0 +1,123 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed 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 + * + * https://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 com.taobao.arthas.core.env; + +/** + * Interface for resolving properties against any underlying source. + * + * @author Chris Beams + * @author Juergen Hoeller + * @since 3.1 + * @see Environment + * @see PropertySourcesPropertyResolver + */ +public interface PropertyResolver { + + /** + * Return whether the given property key is available for resolution, i.e. if + * the value for the given key is not {@code null}. + */ + boolean containsProperty(String key); + + /** + * Return the property value associated with the given key, or {@code null} if + * the key cannot be resolved. + * + * @param key the property name to resolve + * @see #getProperty(String, String) + * @see #getProperty(String, Class) + * @see #getRequiredProperty(String) + */ + String getProperty(String key); + + /** + * Return the property value associated with the given key, or + * {@code defaultValue} if the key cannot be resolved. + * + * @param key the property name to resolve + * @param defaultValue the default value to return if no value is found + * @see #getRequiredProperty(String) + * @see #getProperty(String, Class) + */ + String getProperty(String key, String defaultValue); + + /** + * Return the property value associated with the given key, or {@code null} if + * the key cannot be resolved. + * + * @param key the property name to resolve + * @param targetType the expected type of the property value + * @see #getRequiredProperty(String, Class) + */ + T getProperty(String key, Class targetType); + + /** + * Return the property value associated with the given key, or + * {@code defaultValue} if the key cannot be resolved. + * + * @param key the property name to resolve + * @param targetType the expected type of the property value + * @param defaultValue the default value to return if no value is found + * @see #getRequiredProperty(String, Class) + */ + T getProperty(String key, Class targetType, T defaultValue); + + /** + * Return the property value associated with the given key (never {@code null}). + * + * @throws IllegalStateException if the key cannot be resolved + * @see #getRequiredProperty(String, Class) + */ + String getRequiredProperty(String key) throws IllegalStateException; + + /** + * Return the property value associated with the given key, converted to the + * given targetType (never {@code null}). + * + * @throws IllegalStateException if the given key cannot be resolved + */ + T getRequiredProperty(String key, Class targetType) throws IllegalStateException; + + /** + * Resolve ${...} placeholders in the given text, replacing them with + * corresponding property values as resolved by {@link #getProperty}. + * Unresolvable placeholders with no default value are ignored and passed + * through unchanged. + * + * @param text the String to resolve + * @return the resolved String (never {@code null}) + * @throws IllegalArgumentException if given text is {@code null} + * @see #resolveRequiredPlaceholders + * @see org.springframework.util.SystemPropertyUtils#resolvePlaceholders(String) + */ + String resolvePlaceholders(String text); + + /** + * Resolve ${...} placeholders in the given text, replacing them with + * corresponding property values as resolved by {@link #getProperty}. + * Unresolvable placeholders with no default value will cause an + * IllegalArgumentException to be thrown. + * + * @return the resolved String (never {@code null}) + * @throws IllegalArgumentException if given text is {@code null} or if any + * placeholders are unresolvable + * @see org.springframework.util.SystemPropertyUtils#resolvePlaceholders(String, + * boolean) + */ + String resolveRequiredPlaceholders(String text) throws IllegalArgumentException; + +} diff --git a/core/src/main/java/com/taobao/arthas/core/env/PropertySource.java b/core/src/main/java/com/taobao/arthas/core/env/PropertySource.java new file mode 100644 index 000000000..c86153d8c --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/env/PropertySource.java @@ -0,0 +1,327 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed 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 + * + * https://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 com.taobao.arthas.core.env; + +import java.util.Arrays; + +import com.sun.tools.javac.util.Log; + +/** + * Abstract base class representing a source of name/value property pairs. The + * underlying {@linkplain #getSource() source object} may be of any type + * {@code T} that encapsulates properties. Examples include + * {@link java.util.Properties} objects, {@link java.util.Map} objects, + * {@code ServletContext} and {@code ServletConfig} objects (for access to init + * parameters). Explore the {@code PropertySource} type hierarchy to see + * provided implementations. + * + *

+ * {@code PropertySource} objects are not typically used in isolation, but + * rather through a {@link PropertySources} object, which aggregates property + * sources and in conjunction with a {@link PropertyResolver} implementation + * that can perform precedence-based searches across the set of + * {@code PropertySources}. + * + *

+ * {@code PropertySource} identity is determined not based on the content of + * encapsulated properties, but rather based on the {@link #getName() name} of + * the {@code PropertySource} alone. This is useful for manipulating + * {@code PropertySource} objects when in collection contexts. See operations in + * {@link MutablePropertySources} as well as the {@link #named(String)} and + * {@link #toString()} methods for details. + * + *

+ * Note that when working + * with @{@link org.springframework.context.annotation.Configuration + * Configuration} classes that + * the @{@link org.springframework.context.annotation.PropertySource + * PropertySource} annotation provides a convenient and declarative way of + * adding property sources to the enclosing {@code Environment}. + * + * @author Chris Beams + * @since 3.1 + * @param the source type + * @see PropertySources + * @see PropertyResolver + * @see PropertySourcesPropertyResolver + * @see MutablePropertySources + * @see org.springframework.context.annotation.PropertySource + */ +public abstract class PropertySource { + + protected final String name; + + protected final T source; + + /** + * Create a new {@code PropertySource} with the given name and source object. + */ + public PropertySource(String name, T source) { + this.name = name; + this.source = source; + } + + /** + * Create a new {@code PropertySource} with the given name and with a new + * {@code Object} instance as the underlying source. + *

+ * Often useful in testing scenarios when creating anonymous implementations + * that never query an actual source but rather return hard-coded values. + */ + @SuppressWarnings("unchecked") + public PropertySource(String name) { + this(name, (T) new Object()); + } + + /** + * Return the name of this {@code PropertySource}. + */ + public String getName() { + return this.name; + } + + /** + * Return the underlying source object for this {@code PropertySource}. + */ + public T getSource() { + return this.source; + } + + /** + * Return whether this {@code PropertySource} contains the given name. + *

+ * This implementation simply checks for a {@code null} return value from + * {@link #getProperty(String)}. Subclasses may wish to implement a more + * efficient algorithm if possible. + * + * @param name the property name to find + */ + public boolean containsProperty(String name) { + return (getProperty(name) != null); + } + + /** + * Return the value associated with the given name, or {@code null} if not + * found. + * + * @param name the property to find + * @see PropertyResolver#getRequiredProperty(String) + */ + public abstract Object getProperty(String name); + + /** + * This {@code PropertySource} object is equal to the given object if: + *

    + *
  • they are the same instance + *
  • the {@code name} properties for both objects are equal + *
+ *

+ * No properties other than {@code name} are evaluated. + */ + @Override + public boolean equals(Object other) { + return (this == other + || (other instanceof PropertySource && nullSafeEquals(this.name, ((PropertySource) other).name))); + } + + /** + * Return a hash code derived from the {@code name} property of this + * {@code PropertySource} object. + */ + @Override + public int hashCode() { + return this.name.hashCode(); + } + + /** + * Produce concise output (type and name) if the current log level does not + * include debug. If debug is enabled, produce verbose output including the hash + * code of the PropertySource instance and every name/value property pair. + *

+ * This variable verbosity is useful as a property source such as system + * properties or environment variables may contain an arbitrary number of + * property pairs, potentially leading to difficult to read exception and log + * messages. + * + * @see Log#isDebugEnabled() + */ + @Override + public String toString() { + return getClass().getSimpleName() + " {name='" + this.name + "'}"; + } + + /** + * Return a {@code PropertySource} implementation intended for collection + * comparison purposes only. + *

+ * Primarily for internal use, but given a collection of {@code PropertySource} + * objects, may be used as follows: + * + *

+     * {
+     *     @code
+     *     List> sources = new ArrayList>();
+     *     sources.add(new MapPropertySource("sourceA", mapA));
+     *     sources.add(new MapPropertySource("sourceB", mapB));
+     *     assert sources.contains(PropertySource.named("sourceA"));
+     *     assert sources.contains(PropertySource.named("sourceB"));
+     *     assert !sources.contains(PropertySource.named("sourceC"));
+     * }
+     * 
+ * + * The returned {@code PropertySource} will throw + * {@code UnsupportedOperationException} if any methods other than + * {@code equals(Object)}, {@code hashCode()}, and {@code toString()} are + * called. + * + * @param name the name of the comparison {@code PropertySource} to be created + * and returned. + */ + public static PropertySource named(String name) { + return new ComparisonPropertySource(name); + } + + /** + * Determine if the given objects are equal, returning {@code true} if both are + * {@code null} or {@code false} if only one is {@code null}. + *

+ * Compares arrays with {@code Arrays.equals}, performing an equality check + * based on the array elements rather than the array reference. + * + * @param o1 first Object to compare + * @param o2 second Object to compare + * @return whether the given objects are equal + * @see Object#equals(Object) + * @see java.util.Arrays#equals + */ + public static boolean nullSafeEquals(Object o1, Object o2) { + if (o1 == o2) { + return true; + } + if (o1 == null || o2 == null) { + return false; + } + if (o1.equals(o2)) { + return true; + } + if (o1.getClass().isArray() && o2.getClass().isArray()) { + return arrayEquals(o1, o2); + } + return false; + } + + /** + * Compare the given arrays with {@code Arrays.equals}, performing an equality + * check based on the array elements rather than the array reference. + * + * @param o1 first array to compare + * @param o2 second array to compare + * @return whether the given objects are equal + * @see #nullSafeEquals(Object, Object) + * @see java.util.Arrays#equals + */ + private static boolean arrayEquals(Object o1, Object o2) { + if (o1 instanceof Object[] && o2 instanceof Object[]) { + return Arrays.equals((Object[]) o1, (Object[]) o2); + } + if (o1 instanceof boolean[] && o2 instanceof boolean[]) { + return Arrays.equals((boolean[]) o1, (boolean[]) o2); + } + if (o1 instanceof byte[] && o2 instanceof byte[]) { + return Arrays.equals((byte[]) o1, (byte[]) o2); + } + if (o1 instanceof char[] && o2 instanceof char[]) { + return Arrays.equals((char[]) o1, (char[]) o2); + } + if (o1 instanceof double[] && o2 instanceof double[]) { + return Arrays.equals((double[]) o1, (double[]) o2); + } + if (o1 instanceof float[] && o2 instanceof float[]) { + return Arrays.equals((float[]) o1, (float[]) o2); + } + if (o1 instanceof int[] && o2 instanceof int[]) { + return Arrays.equals((int[]) o1, (int[]) o2); + } + if (o1 instanceof long[] && o2 instanceof long[]) { + return Arrays.equals((long[]) o1, (long[]) o2); + } + if (o1 instanceof short[] && o2 instanceof short[]) { + return Arrays.equals((short[]) o1, (short[]) o2); + } + return false; + } + + /** + * {@code PropertySource} to be used as a placeholder in cases where an actual + * property source cannot be eagerly initialized at application context creation + * time. For example, a {@code ServletContext}-based property source must wait + * until the {@code ServletContext} object is available to its enclosing + * {@code ApplicationContext}. In such cases, a stub should be used to hold the + * intended default position/order of the property source, then be replaced + * during context refresh. + * + * @see org.springframework.context.support.AbstractApplicationContext#initPropertySources() + * @see org.springframework.web.context.support.StandardServletEnvironment + * @see org.springframework.web.context.support.ServletContextPropertySource + */ + public static class StubPropertySource extends PropertySource { + + public StubPropertySource(String name) { + super(name, new Object()); + } + + /** + * Always returns {@code null}. + */ + @Override + public String getProperty(String name) { + return null; + } + } + + /** + * @see PropertySource#named(String) + */ + static class ComparisonPropertySource extends StubPropertySource { + + private static final String USAGE_ERROR = "ComparisonPropertySource instances are for use with collection comparison only"; + + public ComparisonPropertySource(String name) { + super(name); + } + + @Override + public Object getSource() { + throw new UnsupportedOperationException(USAGE_ERROR); + } + + @Override + public boolean containsProperty(String name) { + throw new UnsupportedOperationException(USAGE_ERROR); + } + + @Override + public String getProperty(String name) { + throw new UnsupportedOperationException(USAGE_ERROR); + } + + @Override + public String toString() { + return String.format("%s [name='%s']", getClass().getSimpleName(), this.name); + } + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/env/PropertySources.java b/core/src/main/java/com/taobao/arthas/core/env/PropertySources.java new file mode 100644 index 000000000..480c0caff --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/env/PropertySources.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed 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 + * + * https://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 com.taobao.arthas.core.env; + +/** + * Holder containing one or more {@link PropertySource} objects. + * + * @author Chris Beams + * @author Juergen Hoeller + * @since 3.1 + * @see PropertySource + */ +public interface PropertySources extends Iterable> { + + /** + * Return whether a property source with the given name is contained. + * + * @param name the {@linkplain PropertySource#getName() name of the property + * source} to find + */ + boolean contains(String name); + + /** + * Return the property source with the given name, {@code null} if not found. + * + * @param name the {@linkplain PropertySource#getName() name of the property + * source} to find + */ + PropertySource get(String name); + +} diff --git a/core/src/main/java/com/taobao/arthas/core/env/PropertySourcesPropertyResolver.java b/core/src/main/java/com/taobao/arthas/core/env/PropertySourcesPropertyResolver.java new file mode 100644 index 000000000..3d1f6042d --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/env/PropertySourcesPropertyResolver.java @@ -0,0 +1,129 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed 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 + * + * https://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 com.taobao.arthas.core.env; + +/** + * {@link PropertyResolver} implementation that resolves property values against + * an underlying set of {@link PropertySources}. + * + * @author Chris Beams + * @author Juergen Hoeller + * @since 3.1 + * @see PropertySource + * @see PropertySources + * @see AbstractEnvironment + */ +public class PropertySourcesPropertyResolver extends AbstractPropertyResolver { + + private final PropertySources propertySources; + + /** + * Create a new resolver against the given property sources. + * + * @param propertySources the set of {@link PropertySource} objects to use + */ + public PropertySourcesPropertyResolver(PropertySources propertySources) { + this.propertySources = propertySources; + } + + @Override + public boolean containsProperty(String key) { + if (this.propertySources != null) { + for (PropertySource propertySource : this.propertySources) { + if (propertySource.containsProperty(key)) { + return true; + } + } + } + return false; + } + + @Override + public String getProperty(String key) { + return getProperty(key, String.class, true); + } + + @Override + public T getProperty(String key, Class targetValueType) { + return getProperty(key, targetValueType, true); + } + + @Override + protected String getPropertyAsRawString(String key) { + return getProperty(key, String.class, false); + } + +// protected T getProperty(String key, Class targetValueType, boolean resolveNestedPlaceholders) { +// if (this.propertySources != null) { +// for (PropertySource propertySource : this.propertySources) { +// Object value = propertySource.getProperty(key); +// if (value != null) { +// if (resolveNestedPlaceholders && value instanceof String) { +// value = resolveNestedPlaceholders((String) value); +// } +// logKeyFound(key, propertySource, value); +// return convertValueIfNecessary(value, targetValueType); +// } +// } +// } +// return null; +// } + + protected T getProperty(String key, Class targetValueType, boolean resolveNestedPlaceholders) { + if (this.propertySources != null) { + for (PropertySource propertySource : this.propertySources) { + Object value; + if ((value = propertySource.getProperty(key)) != null) { + Class valueType = value.getClass(); + if (resolveNestedPlaceholders && value instanceof String) { + value = resolveNestedPlaceholders((String) value); + } + if (!this.conversionService.canConvert(valueType, targetValueType)) { + throw new IllegalArgumentException( + String.format("Cannot convert value [%s] from source type [%s] to target type [%s]", + value, valueType.getSimpleName(), targetValueType.getSimpleName())); + } + return this.conversionService.convert(value, targetValueType); + } + } + } + return null; + } + + /** + * Log the given key as found in the given {@link PropertySource}, resulting in + * the given value. + *

+ * The default implementation writes a debug log message with key and source. As + * of 4.3.3, this does not log the value anymore in order to avoid accidental + * logging of sensitive settings. Subclasses may override this method to change + * the log level and/or log message, including the property's value if desired. + * + * @param key the key found + * @param propertySource the {@code PropertySource} that the key has been found + * in + * @param value the corresponding value + * @since 4.3.1 + */ + protected void logKeyFound(String key, PropertySource propertySource, Object value) { +// if (logger.isDebugEnabled()) { +// logger.debug("Found key '" + key + "' in PropertySource '" + propertySource.getName() + +// "' with value of type " + value.getClass().getSimpleName()); +// } + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/env/ReadOnlySystemAttributesMap.java b/core/src/main/java/com/taobao/arthas/core/env/ReadOnlySystemAttributesMap.java new file mode 100644 index 000000000..a4e31a2ca --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/env/ReadOnlySystemAttributesMap.java @@ -0,0 +1,123 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed 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 + * + * https://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 com.taobao.arthas.core.env; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +/** + * Read-only {@code Map} implementation that is backed by system + * properties or environment variables. + * + *

+ * Used by {@link AbstractApplicationContext} when a {@link SecurityManager} + * prohibits access to {@link System#getProperties()} or + * {@link System#getenv()}. It is for this reason that the implementations of + * {@link #keySet()}, {@link #entrySet()}, and {@link #values()} always return + * empty even though {@link #get(Object)} may in fact return non-null if the + * current security manager allows access to individual keys. + * + * @author Arjen Poutsma + * @author Chris Beams + * @since 3.0 + */ +abstract class ReadOnlySystemAttributesMap implements Map { + + @Override + public boolean containsKey(Object key) { + return (get(key) != null); + } + + /** + * Returns the value to which the specified key is mapped, or {@code null} if + * this map contains no mapping for the key. + * + * @param key the name of the system attribute to retrieve + * @throws IllegalArgumentException if given key is non-String + */ + @Override + public String get(Object key) { + if (!(key instanceof String)) { + throw new IllegalArgumentException( + "Type of key [" + key.getClass().getName() + "] must be java.lang.String"); + } + return getSystemAttribute((String) key); + } + + @Override + public boolean isEmpty() { + return false; + } + + /** + * Template method that returns the underlying system attribute. + *

+ * Implementations typically call {@link System#getProperty(String)} or + * {@link System#getenv(String)} here. + */ + protected abstract String getSystemAttribute(String attributeName); + + // Unsupported + + @Override + public int size() { + throw new UnsupportedOperationException(); + } + + @Override + public String put(String key, String value) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean containsValue(Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public String remove(Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public Set keySet() { + return Collections.emptySet(); + } + + @Override + public void putAll(Map map) { + throw new UnsupportedOperationException(); + } + + @Override + public Collection values() { + return Collections.emptySet(); + } + + @Override + public Set> entrySet() { + return Collections.emptySet(); + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/env/SystemEnvironmentPropertySource.java b/core/src/main/java/com/taobao/arthas/core/env/SystemEnvironmentPropertySource.java new file mode 100644 index 000000000..a1d68cc59 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/env/SystemEnvironmentPropertySource.java @@ -0,0 +1,157 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed 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 + * + * https://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 com.taobao.arthas.core.env; + +import java.util.Map; + +/** + * Specialization of {@link MapPropertySource} designed for use with + * {@linkplain AbstractEnvironment#getSystemEnvironment() system environment + * variables}. Compensates for constraints in Bash and other shells that do not + * allow for variables containing the period character and/or hyphen character; + * also allows for uppercase variations on property names for more idiomatic + * shell use. + * + *

+ * For example, a call to {@code getProperty("foo.bar")} will attempt to find a + * value for the original property or any 'equivalent' property, returning the + * first found: + *

    + *
  • {@code foo.bar} - the original name
  • + *
  • {@code foo_bar} - with underscores for periods (if any)
  • + *
  • {@code FOO.BAR} - original, with upper case
  • + *
  • {@code FOO_BAR} - with underscores and upper case
  • + *
+ * Any hyphen variant of the above would work as well, or even mix dot/hyphen + * variants. + * + *

+ * The same applies for calls to {@link #containsProperty(String)}, which + * returns {@code true} if any of the above properties are present, otherwise + * {@code false}. + * + *

+ * This feature is particularly useful when specifying active or default + * profiles as environment variables. The following is not allowable under Bash: + * + *

+ * spring.profiles.active=p1 java -classpath ... MyApp
+ * 
+ * + * However, the following syntax is permitted and is also more conventional: + * + *
+ * SPRING_PROFILES_ACTIVE=p1 java -classpath ... MyApp
+ * 
+ * + *

+ * Enable debug- or trace-level logging for this class (or package) for messages + * explaining when these 'property name resolutions' occur. + * + *

+ * This property source is included by default in {@link StandardEnvironment} + * and all its subclasses. + * + * @author Chris Beams + * @author Juergen Hoeller + * @since 3.1 + * @see StandardEnvironment + * @see AbstractEnvironment#getSystemEnvironment() + * @see AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME + */ +public class SystemEnvironmentPropertySource extends MapPropertySource { + + /** + * Create a new {@code SystemEnvironmentPropertySource} with the given name and + * delegating to the given {@code MapPropertySource}. + */ + public SystemEnvironmentPropertySource(String name, Map source) { + super(name, source); + } + + /** + * Return {@code true} if a property with the given name or any + * underscore/uppercase variant thereof exists in this property source. + */ + @Override + public boolean containsProperty(String name) { + return (getProperty(name) != null); + } + + /** + * This implementation returns {@code true} if a property with the given name or + * any underscore/uppercase variant thereof exists in this property source. + */ + @Override + public Object getProperty(String name) { + String actualName = resolvePropertyName(name); + return super.getProperty(actualName); + } + + /** + * Check to see if this property source contains a property with the given name, + * or any underscore / uppercase variation thereof. Return the resolved name if + * one is found or otherwise the original name. Never returns {@code null}. + */ + protected final String resolvePropertyName(String name) { + String resolvedName = checkPropertyName(name); + if (resolvedName != null) { + return resolvedName; + } + String uppercasedName = name.toUpperCase(); + if (!name.equals(uppercasedName)) { + resolvedName = checkPropertyName(uppercasedName); + if (resolvedName != null) { + return resolvedName; + } + } + return name; + } + + private String checkPropertyName(String name) { + // Check name as-is + if (containsKey(name)) { + return name; + } + // Check name with just dots replaced + String noDotName = name.replace('.', '_'); + if (!name.equals(noDotName) && containsKey(noDotName)) { + return noDotName; + } + // Check name with just hyphens replaced + String noHyphenName = name.replace('-', '_'); + if (!name.equals(noHyphenName) && containsKey(noHyphenName)) { + return noHyphenName; + } + // Check name with dots and hyphens replaced + String noDotNoHyphenName = noDotName.replace('-', '_'); + if (!noDotName.equals(noDotNoHyphenName) && containsKey(noDotNoHyphenName)) { + return noDotNoHyphenName; + } + // Give up + return null; + } + + private boolean containsKey(String name) { + return (isSecurityManagerPresent() ? this.source.keySet().contains(name) : this.source.containsKey(name)); + } + + protected boolean isSecurityManagerPresent() { + return (System.getSecurityManager() != null); + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/env/SystemPropertyUtils.java b/core/src/main/java/com/taobao/arthas/core/env/SystemPropertyUtils.java new file mode 100644 index 000000000..800100dd8 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/env/SystemPropertyUtils.java @@ -0,0 +1,116 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed 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 + * + * https://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 com.taobao.arthas.core.env; + +/** + * Helper class for resolving placeholders in texts. Usually applied to file + * paths. + * + *

+ * A text may contain {@code ${...}} placeholders, to be resolved as system + * properties: e.g. {@code ${user.dir}}. Default values can be supplied using + * the ":" separator between key and value. + * + * @author Juergen Hoeller + * @author Rob Harrop + * @author Dave Syer + * @since 1.2.5 + * @see #PLACEHOLDER_PREFIX + * @see #PLACEHOLDER_SUFFIX + * @see System#getProperty(String) + */ +public abstract class SystemPropertyUtils { + + /** Prefix for system property placeholders: "${". */ + public static final String PLACEHOLDER_PREFIX = "${"; + + /** Suffix for system property placeholders: "}". */ + public static final String PLACEHOLDER_SUFFIX = "}"; + + /** Value separator for system property placeholders: ":". */ + public static final String VALUE_SEPARATOR = ":"; + + private static final PropertyPlaceholderHelper strictHelper = new PropertyPlaceholderHelper(PLACEHOLDER_PREFIX, + PLACEHOLDER_SUFFIX, VALUE_SEPARATOR, false); + + private static final PropertyPlaceholderHelper nonStrictHelper = new PropertyPlaceholderHelper(PLACEHOLDER_PREFIX, + PLACEHOLDER_SUFFIX, VALUE_SEPARATOR, true); + + /** + * Resolve {@code ${...}} placeholders in the given text, replacing them with + * corresponding system property values. + * + * @param text the String to resolve + * @return the resolved String + * @throws IllegalArgumentException if there is an unresolvable placeholder + * @see #PLACEHOLDER_PREFIX + * @see #PLACEHOLDER_SUFFIX + */ + public static String resolvePlaceholders(String text) { + return resolvePlaceholders(text, false); + } + + /** + * Resolve {@code ${...}} placeholders in the given text, replacing them with + * corresponding system property values. Unresolvable placeholders with no + * default value are ignored and passed through unchanged if the flag is set to + * {@code true}. + * + * @param text the String to resolve + * @param ignoreUnresolvablePlaceholders whether unresolved placeholders are to + * be ignored + * @return the resolved String + * @throws IllegalArgumentException if there is an unresolvable placeholder + * @see #PLACEHOLDER_PREFIX + * @see #PLACEHOLDER_SUFFIX and the "ignoreUnresolvablePlaceholders" flag is + * {@code false} + */ + public static String resolvePlaceholders(String text, boolean ignoreUnresolvablePlaceholders) { + PropertyPlaceholderHelper helper = (ignoreUnresolvablePlaceholders ? nonStrictHelper : strictHelper); + return helper.replacePlaceholders(text, new SystemPropertyPlaceholderResolver(text)); + } + + /** + * PlaceholderResolver implementation that resolves against system properties + * and system environment variables. + */ + private static class SystemPropertyPlaceholderResolver implements PropertyPlaceholderHelper.PlaceholderResolver { + + private final String text; + + public SystemPropertyPlaceholderResolver(String text) { + this.text = text; + } + + @Override + public String resolvePlaceholder(String placeholderName) { + try { + String propVal = System.getProperty(placeholderName); + if (propVal == null) { + // Fall back to searching the system environment. + propVal = System.getenv(placeholderName); + } + return propVal; + } catch (Throwable ex) { + System.err.println("Could not resolve placeholder '" + placeholderName + "' in [" + this.text + + "] as system property: " + ex); + return null; + } + } + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/env/package-info.java b/core/src/main/java/com/taobao/arthas/core/env/package-info.java new file mode 100644 index 000000000..867f58c4a --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/env/package-info.java @@ -0,0 +1,5 @@ + +/** + * from org.springframework.core.env + */ +package com.taobao.arthas.core.env; \ No newline at end of file diff --git a/core/src/test/java/com/taobao/arthas/core/env/ArthasEnvironmentTest.java b/core/src/test/java/com/taobao/arthas/core/env/ArthasEnvironmentTest.java new file mode 100644 index 000000000..e208288e5 --- /dev/null +++ b/core/src/test/java/com/taobao/arthas/core/env/ArthasEnvironmentTest.java @@ -0,0 +1,54 @@ +package com.taobao.arthas.core.env; + +import java.util.Properties; + +import org.assertj.core.api.Assertions; +import org.junit.Test; + +/** + * + * @author hengyunabc 2019-12-27 + * + */ +public class ArthasEnvironmentTest { + + @Test + public void test() { + ArthasEnvironment arthasEnvironment = new ArthasEnvironment(); + + Assertions.assertThat(arthasEnvironment.resolvePlaceholders("hello, ${java.version}")) + .isEqualTo("hello, " + System.getProperty("java.version")); + + Assertions.assertThat(arthasEnvironment.resolvePlaceholders("hello, ${xxxxxxxxxxxxxxx}")) + .isEqualTo("hello, ${xxxxxxxxxxxxxxx}"); + + System.setProperty("xxxxxxxxxxxxxxx", "vvv"); + + Assertions.assertThat(arthasEnvironment.resolvePlaceholders("hello, ${xxxxxxxxxxxxxxx}")) + .isEqualTo("hello, vvv"); + + System.clearProperty("xxxxxxxxxxxxxxx"); + } + + @Test + public void test_properties() { + ArthasEnvironment arthasEnvironment = new ArthasEnvironment(); + + Properties properties1 = new Properties(); + Properties properties2 = new Properties(); + arthasEnvironment.addLast(new PropertiesPropertySource("test1", properties1)); + arthasEnvironment.addLast(new PropertiesPropertySource("test2", properties2)); + + properties2.put("test.key", "2222"); + + Assertions.assertThat(arthasEnvironment.resolvePlaceholders("hello, ${test.key}")).isEqualTo("hello, 2222"); + + properties1.put("java.version", "test"); + properties1.put("test.key", "test"); + + Assertions.assertThat(arthasEnvironment.resolvePlaceholders("hello, ${java.version}")) + .isEqualTo("hello, " + System.getProperty("java.version")); + + Assertions.assertThat(arthasEnvironment.resolvePlaceholders("hello, ${test.key}")).isEqualTo("hello, test"); + } +} diff --git a/pom.xml b/pom.xml index 703f4a8f1..840197282 100644 --- a/pom.xml +++ b/pom.xml @@ -160,6 +160,13 @@ 4.11 test + + org.assertj + assertj-core + + 3.11.1 + test + io.netty