001/* 002 * Copyright 2012-2018 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 * http://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.boot.env; 018 019import java.util.Collection; 020import java.util.LinkedHashMap; 021import java.util.Map; 022import java.util.Objects; 023 024import org.springframework.boot.SpringApplication; 025import org.springframework.boot.json.JsonParser; 026import org.springframework.boot.json.JsonParserFactory; 027import org.springframework.boot.origin.Origin; 028import org.springframework.boot.origin.OriginLookup; 029import org.springframework.boot.origin.PropertySourceOrigin; 030import org.springframework.core.Ordered; 031import org.springframework.core.env.ConfigurableEnvironment; 032import org.springframework.core.env.Environment; 033import org.springframework.core.env.MapPropertySource; 034import org.springframework.core.env.MutablePropertySources; 035import org.springframework.core.env.PropertySource; 036import org.springframework.core.env.StandardEnvironment; 037import org.springframework.util.ClassUtils; 038import org.springframework.util.StringUtils; 039import org.springframework.web.context.support.StandardServletEnvironment; 040 041/** 042 * An {@link EnvironmentPostProcessor} that parses JSON from 043 * {@code spring.application.json} or equivalently {@code SPRING_APPLICATION_JSON} and 044 * adds it as a map property source to the {@link Environment}. The new properties are 045 * added with higher priority than the system properties. 046 * 047 * @author Dave Syer 048 * @author Phillip Webb 049 * @author Madhura Bhave 050 * @author Artsiom Yudovin 051 * @since 1.3.0 052 */ 053public class SpringApplicationJsonEnvironmentPostProcessor 054 implements EnvironmentPostProcessor, Ordered { 055 056 /** 057 * Name of the {@code spring.application.json} property. 058 */ 059 public static final String SPRING_APPLICATION_JSON_PROPERTY = "spring.application.json"; 060 061 /** 062 * Name of the {@code SPRING_APPLICATION_JSON} environment variable. 063 */ 064 public static final String SPRING_APPLICATION_JSON_ENVIRONMENT_VARIABLE = "SPRING_APPLICATION_JSON"; 065 066 private static final String SERVLET_ENVIRONMENT_CLASS = "org.springframework.web." 067 + "context.support.StandardServletEnvironment"; 068 069 /** 070 * The default order for the processor. 071 */ 072 public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 5; 073 074 private int order = DEFAULT_ORDER; 075 076 @Override 077 public int getOrder() { 078 return this.order; 079 } 080 081 public void setOrder(int order) { 082 this.order = order; 083 } 084 085 @Override 086 public void postProcessEnvironment(ConfigurableEnvironment environment, 087 SpringApplication application) { 088 MutablePropertySources propertySources = environment.getPropertySources(); 089 propertySources.stream().map(JsonPropertyValue::get).filter(Objects::nonNull) 090 .findFirst().ifPresent((v) -> processJson(environment, v)); 091 } 092 093 private void processJson(ConfigurableEnvironment environment, 094 JsonPropertyValue propertyValue) { 095 JsonParser parser = JsonParserFactory.getJsonParser(); 096 Map<String, Object> map = parser.parseMap(propertyValue.getJson()); 097 if (!map.isEmpty()) { 098 addJsonPropertySource(environment, 099 new JsonPropertySource(propertyValue, flatten(map))); 100 } 101 } 102 103 /** 104 * Flatten the map keys using period separator. 105 * @param map the map that should be flattened 106 * @return the flattened map 107 */ 108 private Map<String, Object> flatten(Map<String, Object> map) { 109 Map<String, Object> result = new LinkedHashMap<>(); 110 flatten(null, result, map); 111 return result; 112 } 113 114 private void flatten(String prefix, Map<String, Object> result, 115 Map<String, Object> map) { 116 String namePrefix = (prefix != null) ? prefix + "." : ""; 117 map.forEach((key, value) -> extract(namePrefix + key, result, value)); 118 } 119 120 @SuppressWarnings("unchecked") 121 private void extract(String name, Map<String, Object> result, Object value) { 122 if (value instanceof Map) { 123 flatten(name, result, (Map<String, Object>) value); 124 } 125 else if (value instanceof Collection) { 126 int index = 0; 127 for (Object object : (Collection<Object>) value) { 128 extract(name + "[" + index + "]", result, object); 129 index++; 130 } 131 } 132 else { 133 result.put(name, value); 134 } 135 } 136 137 private void addJsonPropertySource(ConfigurableEnvironment environment, 138 PropertySource<?> source) { 139 MutablePropertySources sources = environment.getPropertySources(); 140 String name = findPropertySource(sources); 141 if (sources.contains(name)) { 142 sources.addBefore(name, source); 143 } 144 else { 145 sources.addFirst(source); 146 } 147 } 148 149 private String findPropertySource(MutablePropertySources sources) { 150 if (ClassUtils.isPresent(SERVLET_ENVIRONMENT_CLASS, null) && sources 151 .contains(StandardServletEnvironment.JNDI_PROPERTY_SOURCE_NAME)) { 152 return StandardServletEnvironment.JNDI_PROPERTY_SOURCE_NAME; 153 154 } 155 return StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME; 156 } 157 158 private static class JsonPropertySource extends MapPropertySource 159 implements OriginLookup<String> { 160 161 private final JsonPropertyValue propertyValue; 162 163 JsonPropertySource(JsonPropertyValue propertyValue, Map<String, Object> source) { 164 super(SPRING_APPLICATION_JSON_PROPERTY, source); 165 this.propertyValue = propertyValue; 166 } 167 168 @Override 169 public Origin getOrigin(String key) { 170 return this.propertyValue.getOrigin(); 171 } 172 173 } 174 175 private static class JsonPropertyValue { 176 177 private static final String[] CANDIDATES = { SPRING_APPLICATION_JSON_PROPERTY, 178 SPRING_APPLICATION_JSON_ENVIRONMENT_VARIABLE }; 179 180 private final PropertySource<?> propertySource; 181 182 private final String propertyName; 183 184 private final String json; 185 186 JsonPropertyValue(PropertySource<?> propertySource, String propertyName, 187 String json) { 188 this.propertySource = propertySource; 189 this.propertyName = propertyName; 190 this.json = json; 191 } 192 193 public String getJson() { 194 return this.json; 195 } 196 197 public Origin getOrigin() { 198 return PropertySourceOrigin.get(this.propertySource, this.propertyName); 199 } 200 201 public static JsonPropertyValue get(PropertySource<?> propertySource) { 202 for (String candidate : CANDIDATES) { 203 Object value = propertySource.getProperty(candidate); 204 if (value != null && value instanceof String 205 && StringUtils.hasLength((String) value)) { 206 return new JsonPropertyValue(propertySource, candidate, 207 (String) value); 208 } 209 } 210 return null; 211 } 212 213 } 214 215}