001/* 002 * Copyright 2012-2017 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.configurationmetadata; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.nio.charset.Charset; 022import java.util.ArrayList; 023import java.util.List; 024import java.util.Map; 025 026/** 027 * Load a {@link ConfigurationMetadataRepository} from the content of arbitrary 028 * resource(s). 029 * 030 * @author Stephane Nicoll 031 * @since 1.3.0 032 */ 033public final class ConfigurationMetadataRepositoryJsonBuilder { 034 035 /** 036 * UTF-8 Charset. 037 */ 038 public static final Charset UTF_8 = Charset.forName("UTF-8"); 039 040 private Charset defaultCharset = UTF_8; 041 042 private final JsonReader reader = new JsonReader(); 043 044 private final List<SimpleConfigurationMetadataRepository> repositories = new ArrayList<SimpleConfigurationMetadataRepository>(); 045 046 private ConfigurationMetadataRepositoryJsonBuilder(Charset defaultCharset) { 047 this.defaultCharset = defaultCharset; 048 } 049 050 /** 051 * Add the content of a {@link ConfigurationMetadataRepository} defined by the 052 * specified {@link InputStream} json document using the default charset. If this 053 * metadata repository holds items that were loaded previously, these are ignored. 054 * <p> 055 * Leaves the stream open when done. 056 * @param inputStream the source input stream 057 * @return this builder 058 * @throws IOException in case of I/O errors 059 */ 060 public ConfigurationMetadataRepositoryJsonBuilder withJsonResource( 061 InputStream inputStream) throws IOException { 062 return withJsonResource(inputStream, this.defaultCharset); 063 } 064 065 /** 066 * Add the content of a {@link ConfigurationMetadataRepository} defined by the 067 * specified {@link InputStream} json document using the specified {@link Charset}. If 068 * this metadata repository holds items that were loaded previously, these are 069 * ignored. 070 * <p> 071 * Leaves the stream open when done. 072 * @param inputStream the source input stream 073 * @param charset the charset of the input 074 * @return this builder 075 * @throws IOException in case of I/O errors 076 */ 077 public ConfigurationMetadataRepositoryJsonBuilder withJsonResource( 078 InputStream inputStream, Charset charset) throws IOException { 079 if (inputStream == null) { 080 throw new IllegalArgumentException("InputStream must not be null."); 081 } 082 this.repositories.add(add(inputStream, charset)); 083 return this; 084 } 085 086 /** 087 * Build a {@link ConfigurationMetadataRepository} with the current state of this 088 * builder. 089 * @return this builder 090 */ 091 public ConfigurationMetadataRepository build() { 092 SimpleConfigurationMetadataRepository result = new SimpleConfigurationMetadataRepository(); 093 for (SimpleConfigurationMetadataRepository repository : this.repositories) { 094 result.include(repository); 095 } 096 return result; 097 } 098 099 private SimpleConfigurationMetadataRepository add(InputStream in, Charset charset) 100 throws IOException { 101 try { 102 RawConfigurationMetadata metadata = this.reader.read(in, charset); 103 return create(metadata); 104 } 105 catch (Exception ex) { 106 throw new IllegalStateException("Failed to read configuration metadata", ex); 107 } 108 } 109 110 private SimpleConfigurationMetadataRepository create( 111 RawConfigurationMetadata metadata) { 112 SimpleConfigurationMetadataRepository repository = new SimpleConfigurationMetadataRepository(); 113 repository.add(metadata.getSources()); 114 for (ConfigurationMetadataItem item : metadata.getItems()) { 115 ConfigurationMetadataSource source = getSource(metadata, item); 116 repository.add(item, source); 117 } 118 Map<String, ConfigurationMetadataProperty> allProperties = repository 119 .getAllProperties(); 120 for (ConfigurationMetadataHint hint : metadata.getHints()) { 121 ConfigurationMetadataProperty property = allProperties.get(hint.getId()); 122 if (property != null) { 123 addValueHints(property, hint); 124 } 125 else { 126 String id = hint.resolveId(); 127 property = allProperties.get(id); 128 if (property != null) { 129 if (hint.isMapKeyHints()) { 130 addMapHints(property, hint); 131 } 132 else { 133 addValueHints(property, hint); 134 } 135 } 136 } 137 } 138 return repository; 139 } 140 141 private void addValueHints(ConfigurationMetadataProperty property, 142 ConfigurationMetadataHint hint) { 143 property.getHints().getValueHints().addAll(hint.getValueHints()); 144 property.getHints().getValueProviders().addAll(hint.getValueProviders()); 145 } 146 147 private void addMapHints(ConfigurationMetadataProperty property, 148 ConfigurationMetadataHint hint) { 149 property.getHints().getKeyHints().addAll(hint.getValueHints()); 150 property.getHints().getKeyProviders().addAll(hint.getValueProviders()); 151 } 152 153 private ConfigurationMetadataSource getSource(RawConfigurationMetadata metadata, 154 ConfigurationMetadataItem item) { 155 if (item.getSourceType() != null) { 156 return metadata.getSource(item.getSourceType()); 157 } 158 return null; 159 } 160 161 /** 162 * Create a new builder instance using {@link #UTF_8} as the default charset and the 163 * specified json resource. 164 * @param inputStreams the source input streams 165 * @return a new {@link ConfigurationMetadataRepositoryJsonBuilder} instance. 166 * @throws IOException on error 167 */ 168 public static ConfigurationMetadataRepositoryJsonBuilder create( 169 InputStream... inputStreams) throws IOException { 170 ConfigurationMetadataRepositoryJsonBuilder builder = create(); 171 for (InputStream inputStream : inputStreams) { 172 builder = builder.withJsonResource(inputStream); 173 } 174 return builder; 175 } 176 177 /** 178 * Create a new builder instance using {@link #UTF_8} as the default charset. 179 * @return a new {@link ConfigurationMetadataRepositoryJsonBuilder} instance. 180 */ 181 public static ConfigurationMetadataRepositoryJsonBuilder create() { 182 return create(UTF_8); 183 } 184 185 /** 186 * Create a new builder instance using the specified default {@link Charset}. 187 * @param defaultCharset the default charset to use 188 * @return a new {@link ConfigurationMetadataRepositoryJsonBuilder} instance. 189 */ 190 public static ConfigurationMetadataRepositoryJsonBuilder create( 191 Charset defaultCharset) { 192 return new ConfigurationMetadataRepositoryJsonBuilder(defaultCharset); 193 } 194 195}