/* * Copyright 2013-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 org.springframework.xd.module.core; import static org.springframework.core.env.StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME; import static org.springframework.core.env.StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.env.AbstractEnvironment; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.Environment; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.PropertySource; /** * A dedicated {@link Environment} for a module, which restricts the values exposed to the * {@link PropertySourcesPlaceholderConfigurer} living in the module context. * * <p> * In particular, this prevents scenarios where <i>e.g.</i> the module would have ${username} in its definition, where * <i>username</i> is a valid module option name for which there is no current value (PS returns {@code null} but there * exists say, an environment variable of the same name. In such a case, the module would pick up that value by mistake. * </p> * * See https://jira.spring.io/browse/XD-1459. * * @author Eric Bottard * @author David Turanski */ public class ModuleEnvironment extends AbstractEnvironment { private ConfigurableEnvironment parent; public ModuleEnvironment(EnumerablePropertySource<?> moduleOptionsPropertySource, ConfigurableEnvironment parent) { this.parent = parent; if (parent != null) { merge(parent); // This will copy profiles (and property sources) } clearPropertySources(); // We don't want the property sources though getPropertySources().addFirst(wrap(moduleOptionsPropertySource)); } /** * Wrap the module options property source so that it can be the only one living in the environment. This will * delegate to the parent environment ONLY IF the key is not a known module option name. */ private EnumerablePropertySource<?> wrap(final EnumerablePropertySource<?> moduleOptionsPropertySource) { return new EnumerablePropertySource<Object>(moduleOptionsPropertySource.getName (), moduleOptionsPropertySource) { @Override public String[] getPropertyNames() { return moduleOptionsPropertySource.getPropertyNames(); } @Override public Object getProperty(String name) { // Resolve thru the module first Object result = moduleOptionsPropertySource.getProperty(name); if (result != null) { return result; } // If module could not resolve, but it is *known* to be // a valid module option name, do NOT delegate to the env // as it could end up in false positives if (moduleOptionsPropertySource.containsProperty(name)) { return null; } else { return parent == null ? null : parent.getProperty(name); } } }; } /** * Remove all property sources carried over from the parent environment. Add some empty sources where Spring boot * expects them. */ private void clearPropertySources() { List<String> names = new ArrayList<String>(); for (PropertySource<?> ps : getPropertySources()) { names.add(ps.getName()); } for (String name : names) { getPropertySources().remove(name); } Map<String, Object> empty = Collections.emptyMap(); getPropertySources().addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, empty)); getPropertySources().addLast(new MapPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, empty)); } }