/* * Copyright 2014-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 * * 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.options; import static org.springframework.xd.module.core.CompositeModule.OPTION_SEPARATOR; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.springframework.core.env.EnumerablePropertySource; import org.springframework.validation.BeanPropertyBindingResult; import org.springframework.validation.BindException; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.xd.module.core.CompositeModule; /** * A composite {@link ModuleOptionsMetadata} made of several {@link ModuleOptionsMetadata}, each assigned to a name, * that will appear in a hierarchy. Used to represent options to a composite module. * * @author Eric Bottard * @author Gary Russell */ public class HierarchicalCompositeModuleOptionsMetadata implements ModuleOptionsMetadata { private Map<String, ModuleOptionsMetadata> composedModuleDefinitions; private List<ModuleOption> options = new ArrayList<ModuleOption>(); private final static String HIERARCHICAL_PROFILE_SEPARATOR = "#"; public HierarchicalCompositeModuleOptionsMetadata(Map<String, ModuleOptionsMetadata> hierarchy) { this.composedModuleDefinitions = hierarchy; for (Entry<String, ModuleOptionsMetadata> entry : hierarchy.entrySet()) { for (ModuleOption option : entry.getValue()) { ModuleOption mo = new ModuleOption(entry.getKey() + OPTION_SEPARATOR + option.getName(), option.getDescription()); mo.withDefaultValue(option.getDefaultValue()).withType(option.getType()); options.add(mo); } } } @Override public Iterator<ModuleOption> iterator() { return options.iterator(); } @Override public ModuleOptions interpolate(final Map<String, String> raw) throws BindException { final Map<String, ModuleOptions> delegates = new HashMap<String, ModuleOptions>(); Map<String, Map<String, String>> valuesWithoutPrefix = new HashMap<String, Map<String, String>>(); for (String prefix : composedModuleDefinitions.keySet()) { valuesWithoutPrefix.put(prefix, new HashMap<String, String>()); } BindingResult bindingResult = new BeanPropertyBindingResult(this, "options"); for (Entry<String, String> entry : raw.entrySet()) { String key = entry.getKey(); int separator = key.indexOf(OPTION_SEPARATOR); if (separator == -1) { bindingResult.addError(new FieldError("options", key, String.format("unsupported option '%s'", key))); continue; } String prefix = key.substring(0, separator); String suffix = key.substring(separator + OPTION_SEPARATOR.length()); Map<String, String> map = valuesWithoutPrefix.get(prefix); if (map == null) { bindingResult.addError(new FieldError("options", key, String.format("unsupported option '%s'", key))); continue; } map.put(suffix, entry.getValue()); } if (bindingResult.hasErrors()) { throw new BindException(bindingResult); } for (String prefix : composedModuleDefinitions.keySet()) { Map<String, String> subMap = valuesWithoutPrefix.get(prefix); ModuleOptions interpolated = composedModuleDefinitions.get(prefix).interpolate(subMap); delegates.put(prefix, interpolated); } return new ModuleOptions() { @Override public EnumerablePropertySource<?> asPropertySource() { Map<String, EnumerablePropertySource<?>> pss = new HashMap<String, EnumerablePropertySource<?>>(); for (Entry<String, ModuleOptions> entry : delegates.entrySet()) { pss.put(entry.getKey(), entry.getValue().asPropertySource()); } return new HierarchicalEnumerablePropertySource("foo", pss); } /** * The composed module itself never needs to activate profiles. It's the submodules that should. The keys * returned here are special constructs that are decoded by {@link PrefixNarrowingModuleOptions}. */ @Override public String[] profilesToActivate() { List<String> result = new ArrayList<String>(delegates.size()); for (String prefix : delegates.keySet()) { ModuleOptions delegate = delegates.get(prefix); for (String p : delegate.profilesToActivate()) { result.add(qualifyProfile(prefix, p)); } } return result.toArray(new String[result.size()]); } @Override public void validate() { for (ModuleOptions delegate : delegates.values()) { delegate.validate(); } } }; } /* default */static String qualifyProfile(String prefix, String profile) { return String.format("%s%s%s", prefix, HIERARCHICAL_PROFILE_SEPARATOR, profile); } /* default */static String[] filterQualifiedProfiles(String[] whole, String moduleName) { List<String> result = new ArrayList<String>(); for (String raw : whole) { String prefix = moduleName + HIERARCHICAL_PROFILE_SEPARATOR; if (raw.startsWith(prefix)) { result.add(raw.substring(prefix.length())); } } return result.toArray(new String[result.size()]); } } class HierarchicalEnumerablePropertySource extends EnumerablePropertySource<Object> { private Map<String, EnumerablePropertySource<?>> delegates; public HierarchicalEnumerablePropertySource(String name, Map<String, EnumerablePropertySource<?>> delegates) { super(name, delegates); this.delegates = delegates; } @Override public String[] getPropertyNames() { Set<String> result = new HashSet<String>(delegates.size() * 3); for (String prefix : delegates.keySet()) { String[] sub = delegates.get(prefix).getPropertyNames(); for (String value : sub) { result.add(prefix + CompositeModule.OPTION_SEPARATOR + value); } } return result.toArray(new String[result.size()]); } @Override public Object getProperty(String name) { int separator = name.indexOf(OPTION_SEPARATOR); if (separator == -1) { return null; } String prefix = name.substring(0, separator); EnumerablePropertySource<?> sub = delegates.get(prefix); if (sub == null) { return null; } return sub.getProperty(name.substring(separator + OPTION_SEPARATOR.length())); } }