001/*
002 * Copyright 2002-2016 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.beans.factory.config;
018
019import java.util.LinkedHashMap;
020import java.util.Map;
021import java.util.Map.Entry;
022import java.util.Properties;
023
024import org.springframework.beans.factory.FactoryBean;
025import org.springframework.beans.factory.InitializingBean;
026
027/**
028 * Factory for a {@code Map} that reads from a YAML source, preserving the
029 * YAML-declared value types and their structure.
030 *
031 * <p>YAML is a nice human-readable format for configuration, and it has some
032 * useful hierarchical properties. It's more or less a superset of JSON, so it
033 * has a lot of similar features.
034 *
035 * <p>If multiple resources are provided the later ones will override entries in
036 * the earlier ones hierarchically; that is, all entries with the same nested key
037 * of type {@code Map} at any depth are merged. For example:
038 *
039 * <pre class="code">
040 * foo:
041 *   bar:
042 *    one: two
043 * three: four
044 * </pre>
045 *
046 * plus (later in the list)
047 *
048 * <pre class="code">
049 * foo:
050 *   bar:
051 *    one: 2
052 * five: six
053 * </pre>
054 *
055 * results in an effective input of
056 *
057 * <pre class="code">
058 * foo:
059 *   bar:
060 *    one: 2
061 * three: four
062 * five: six
063 * </pre>
064 *
065 * Note that the value of "foo" in the first document is not simply replaced
066 * with the value in the second, but its nested values are merged.
067 *
068 * @author Dave Syer
069 * @author Juergen Hoeller
070 * @since 4.1
071 */
072public class YamlMapFactoryBean extends YamlProcessor implements FactoryBean<Map<String, Object>>, InitializingBean {
073
074        private boolean singleton = true;
075
076        private Map<String, Object> map;
077
078
079        /**
080         * Set if a singleton should be created, or a new object on each request
081         * otherwise. Default is {@code true} (a singleton).
082         */
083        public void setSingleton(boolean singleton) {
084                this.singleton = singleton;
085        }
086
087        @Override
088        public boolean isSingleton() {
089                return this.singleton;
090        }
091
092        @Override
093        public void afterPropertiesSet() {
094                if (isSingleton()) {
095                        this.map = createMap();
096                }
097        }
098
099        @Override
100        public Map<String, Object> getObject() {
101                return (this.map != null ? this.map : createMap());
102        }
103
104        @Override
105        public Class<?> getObjectType() {
106                return Map.class;
107        }
108
109
110        /**
111         * Template method that subclasses may override to construct the object
112         * returned by this factory.
113         * <p>Invoked lazily the first time {@link #getObject()} is invoked in
114         * case of a shared singleton; else, on each {@link #getObject()} call.
115         * <p>The default implementation returns the merged {@code Map} instance.
116         * @return the object returned by this factory
117         * @see #process(java.util.Map, MatchCallback)
118         */
119        protected Map<String, Object> createMap() {
120                final Map<String, Object> result = new LinkedHashMap<String, Object>();
121                process(new MatchCallback() {
122                        @Override
123                        public void process(Properties properties, Map<String, Object> map) {
124                                merge(result, map);
125                        }
126                });
127                return result;
128        }
129
130        @SuppressWarnings({"unchecked", "rawtypes"})
131        private void merge(Map<String, Object> output, Map<String, Object> map) {
132                for (Entry<String, Object> entry : map.entrySet()) {
133                        String key = entry.getKey();
134                        Object value = entry.getValue();
135                        Object existing = output.get(key);
136                        if (value instanceof Map && existing instanceof Map) {
137                                Map<String, Object> result = new LinkedHashMap<String, Object>((Map) existing);
138                                merge(result, (Map) value);
139                                output.put(key, result);
140                        }
141                        else {
142                                output.put(key, value);
143                        }
144                }
145        }
146
147}