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}