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}