Skip to content

Commit

Permalink
+ Support integer keys in application.yaml
Browse files Browse the repository at this point in the history
+ spring-boot generator can not handle multi-profile configration

Ported PR fabric8io/fabric8-maven-plugin#1745
Ported PR fabric8io/fabric8-maven-plugin#1746
  • Loading branch information
rohanKanojia committed Nov 7, 2019
1 parent 141f267 commit 6b6d777
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,25 +39,38 @@ public class SpringBootUtil {

/**
* Returns the spring boot configuration (supports `application.properties` and `application.yml`)
* or an empty properties object if not found
* or an empty properties object if not found, it assumes first profile as default profile.
*
* @param compileClassLoader URLClassLoader for resource access
* @return spring boot configuration as Properties
* @param compileClassLoader compile class loader
* @return properties object
*/
public static Properties getSpringBootApplicationProperties(URLClassLoader compileClassLoader) {
return getSpringBootApplicationProperties(null, compileClassLoader);
}

/**
* Returns the spring boot configuration (supports `application.properties` and `application.yml`)
* or an empty properties object if not found
*
* @param springActiveProfile currently active spring-boot profile
* @param compileClassLoader compile class loader
* @return properties object
*/
public static Properties getSpringBootApplicationProperties(String springActiveProfile, URLClassLoader compileClassLoader) {
URL ymlResource = compileClassLoader.findResource("application.yml");
URL propertiesResource = compileClassLoader.findResource("application.properties");

Properties props = YamlUtil.getPropertiesFromYamlResource(ymlResource);
Properties props = getPropertiesFromApplicationYamlResource(springActiveProfile, ymlResource);
props.putAll(getPropertiesResource(propertiesResource));
return props;
}

public static Properties getPropertiesFromApplicationYamlResource(String springActiveProfile, URL ymlResource) {
return YamlUtil.getPropertiesFromYamlResource(springActiveProfile, ymlResource);
}

/**
* Returns the given properties resource on the project classpath if found or an empty properties object if not
*
* @param resource URL of the resource
* @return Properties resource
*/
protected static Properties getPropertiesResource(URL resource) {
Properties answer = new Properties();
Expand All @@ -73,25 +86,25 @@ protected static Properties getPropertiesResource(URL resource) {

/**
* Determine the spring-boot devtools version for the current project
*
* @param mavenProject MavenProject of that project
* @return optional string having spring boot devtools version
*/
public static Optional<String> getSpringBootDevToolsVersion(MavenProject mavenProject) {
return getSpringBootVersion(mavenProject);
}

/**
* Determine the spring-boot major version for the current project
*
* @param mavenProject Maven Project
* @return optional string having spring boot version
*/
public static Optional<String> getSpringBootVersion(MavenProject mavenProject) {
return Optional.ofNullable(MavenUtil.getDependencyVersion(mavenProject, SpringBootConfigurationHelper.SPRING_BOOT_GROUP_ID, SpringBootConfigurationHelper.SPRING_BOOT_ARTIFACT_ID));
}



public static String getSpringBootActiveProfile(MavenProject mavenProject) {
if (mavenProject != null && mavenProject.getProperties() != null) {
if (mavenProject.getProperties().get("spring.profiles.active") != null) {
return mavenProject.getProperties().get("spring.profiles.active").toString();
}
}
return null;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,42 @@

import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.SortedMap;
import org.yaml.snakeyaml.Yaml;

public class YamlUtil {
protected static Properties getPropertiesFromYamlResource(URL resource) {
return getPropertiesFromYamlResource(null, resource);
}

protected static Properties getPropertiesFromYamlResource(String activeProfile, URL resource) {
if (resource != null) {
try (InputStream yamlStream = resource.openStream()) {
Yaml yaml = new Yaml();
@SuppressWarnings("unchecked")
SortedMap<String, Object> source = yaml.loadAs(yamlStream, SortedMap.class);
try {
Properties properties = new Properties();
if (source != null) {
// Splitting file for the possibility of different profiles, by default
// only first profile would be considered.
List<String> profiles = getYamlListFromFile(resource);
if (profiles.size() > 0) {
try {
properties.putAll(getFlattenedMap(source));
properties.putAll(getPropertiesFromYamlString(getYamlFromYamlList(activeProfile, profiles)));
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(String.format("Spring Boot configuration file %s is not formatted correctly. %s",
resource.toString(), e.getMessage()));
}
}
return properties;
} catch (IOException e) {
} catch (IOException | URISyntaxException e) {
throw new IllegalStateException("Error while reading Yaml resource from URL " + resource, e);
}
}
Expand All @@ -51,25 +60,27 @@ protected static Properties getPropertiesFromYamlResource(URL resource) {
/**
* Build a flattened representation of the Yaml tree. The conversion is compliant with the thorntail spring-boot rules.
*/
private static Map<String, Object> getFlattenedMap(Map<String, Object> source) {
private static Map<String, Object> getFlattenedMap(Map<?, ?> source) {
Map<String, Object> result = new LinkedHashMap<>();
buildFlattenedMap(result, source, null);
return result;
}

@SuppressWarnings("unchecked")
private static void buildFlattenedMap(Map<String, Object> result, Map<String, Object> source, String path) {
for (Map.Entry<String, Object> entry : source.entrySet()) {
private static void buildFlattenedMap(Map<String, Object> result, Map<?, ?> source, String path) {
for (Map.Entry<?, ?> entry : source.entrySet()) {
Object keyObject = entry.getKey();

// If user creates a wrong application.yml then we get a runtime classcastexception
if (!(keyObject instanceof String)) {
String key;
if (keyObject instanceof String) {
key = (String) keyObject;
} else if (keyObject instanceof Number) {
key = String.valueOf(keyObject);
} else {
// If user creates a wrong application.yml then we get a runtime classcastexception
throw new IllegalArgumentException(String.format("Expected to find a key of type String but %s with content %s found.",
keyObject.getClass(), keyObject.toString()));
}

String key = (String) keyObject;

if (path !=null && path.trim().length()>0) {
if (key.startsWith("[")) {
key = path + key;
Expand All @@ -81,11 +92,11 @@ private static void buildFlattenedMap(Map<String, Object> result, Map<String, Ob
Object value = entry.getValue();
if (value instanceof Map) {

Map<String, Object> map = (Map<String, Object>) value;
Map<?, ?> map = (Map<?, ?>) value;
buildFlattenedMap(result, map, key);
}
else if (value instanceof Collection) {
Collection<Object> collection = (Collection<Object>) value;
Collection<?> collection = (Collection<?>) value;
int count = 0;
for (Object object : collection) {
buildFlattenedMap(result,
Expand All @@ -98,5 +109,33 @@ else if (value instanceof Collection) {
}
}

public static Properties getPropertiesFromYamlString(String yamlString) throws IllegalArgumentException {
Yaml yaml = new Yaml();
Properties properties = new Properties();

@SuppressWarnings("unchecked")
SortedMap<String, Object> source = yaml.loadAs(yamlString, SortedMap.class);
if (source != null) {
properties.putAll(getFlattenedMap(source));
}
return properties;
}

public static List<String> getYamlListFromFile(URL resource) throws URISyntaxException, IOException {
String fileAsString = new String(Files.readAllBytes(Paths.get(resource.toURI())));
String[] profiles = fileAsString.split("---");
return Arrays.asList(profiles);
}

public static String getYamlFromYamlList(String pattern, List<String> yamlAsStringList) {
if (pattern != null) {
for (String yamlStr : yamlAsStringList) {
if (yamlStr.contains(pattern))
return yamlStr;
}
}
return yamlAsStringList.get(0);
}

}

Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

import org.junit.Test;

import static junit.framework.TestCase.assertNull;
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
Expand All @@ -39,6 +41,7 @@ public void testYamlToPropertiesParsing() {
assertEquals("value1", props.getProperty("example.nested.items[1].value"));
assertEquals("sub0", props.getProperty("example.nested.items[2].elements[0].element[0].subelement"));
assertEquals("sub1", props.getProperty("example.nested.items[2].elements[0].element[1].subelement"));
assertEquals("integerKeyElement", props.getProperty("example.1"));

}

Expand Down Expand Up @@ -78,4 +81,19 @@ public void testNonExistentPropertiesParsing() {

}

@Test
public void testMultipleProfilesParsing() {
Properties props = SpringBootUtil.getPropertiesFromApplicationYamlResource(null, getClass().getResource("/util/test-application-with-multiple-profiles.yml"));
assertTrue(props.size() > 0);

assertEquals("spring-boot-k8-recipes", props.get("spring.application.name"));
assertEquals("false", props.get("management.endpoints.enabled-by-default"));
assertEquals("true", props.get("management.endpoint.health.enabled"));
assertNull(props.get("cloud.kubernetes.reload.enabled"));

props = SpringBootUtil.getPropertiesFromApplicationYamlResource("kubernetes", getClass().getResource("/util/test-application-with-multiple-profiles.yml"));
assertEquals("true", props.get("cloud.kubernetes.reload.enabled"));
assertNull(props.get("spring.application.name"));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#
# Copyright (c) 2019 Red Hat, Inc.
# This program and the accompanying materials are made
# available under the terms of the Eclipse Public License 2.0
# which is available at:
#
# https://www.eclipse.org/legal/epl-2.0/
#
# SPDX-License-Identifier: EPL-2.0
#
# Contributors:
# Red Hat, Inc. - initial API and implementation
#

spring:
application:
name: spring-boot-k8-recipes

management:
endpoints:
web:
base-path: '/'
enabled-by-default: false
endpoint:
health:
enabled: true
metrics:
enabled: true
---
spring:
profiles: kubernetes
cloud:
kubernetes:
reload:
enabled: true
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,6 @@ example:
- element:
- subelement: sub0
- subelement: sub1
1: integerKeyElement


Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ protected Map<String, String> getEnv(boolean prePackagePhase) throws MojoExecuti
Map<String, String> res = super.getEnv(prePackagePhase);
if (getContext().getGeneratorMode() == GeneratorMode.WATCH) {
// adding dev tools token to env variables to prevent override during recompile
String secret = SpringBootUtil.getSpringBootApplicationProperties(MavenUtil.getCompileClassLoader(getProject())).getProperty(DEV_TOOLS_REMOTE_SECRET);
String secret = SpringBootUtil.getSpringBootApplicationProperties(
SpringBootUtil.getSpringBootActiveProfile(getProject()),
MavenUtil.getCompileClassLoader(getProject())).getProperty(SpringBootConfigurationHelper.DEV_TOOLS_REMOTE_SECRET);
if (secret != null) {
res.put(SpringBootConfigurationHelper.DEV_TOOLS_REMOTE_SECRET_ENV, secret);
}
Expand Down Expand Up @@ -118,7 +120,9 @@ protected boolean isFatJar() throws MojoExecutionException {
@Override
protected List<String> extractPorts() {
List<String> answer = new ArrayList<>();
Properties properties = SpringBootUtil.getSpringBootApplicationProperties(MavenUtil.getCompileClassLoader(this.getProject()));
Properties properties = SpringBootUtil.getSpringBootApplicationProperties(
SpringBootUtil.getSpringBootActiveProfile(getProject()),
MavenUtil.getCompileClassLoader(this.getProject()));
SpringBootConfigurationHelper propertyHelper = new SpringBootConfigurationHelper(SpringBootUtil.getSpringBootVersion(getProject()));
String port = properties.getProperty(propertyHelper.getServerPortPropertyKey(), DEFAULT_SERVER_PORT);
addPortIfValid(answer, getConfig(JavaExecGenerator.Config.webPort, port));
Expand All @@ -130,7 +134,9 @@ protected List<String> extractPorts() {
// =============================================================================

private void ensureSpringDevToolSecretToken() throws MojoExecutionException {
Properties properties = SpringBootUtil.getSpringBootApplicationProperties(MavenUtil.getCompileClassLoader(getProject()));
Properties properties = SpringBootUtil.getSpringBootApplicationProperties(
SpringBootUtil.getSpringBootActiveProfile(getProject()),
MavenUtil.getCompileClassLoader(getProject()));
String remoteSecret = properties.getProperty(DEV_TOOLS_REMOTE_SECRET);
if (Strings.isNullOrEmpty(remoteSecret)) {
addSecretTokenToApplicationProperties();
Expand Down

0 comments on commit 6b6d777

Please sign in to comment.