Skip to content

Commit

Permalink
Merge pull request #6 from Piszmog/develop
Browse files Browse the repository at this point in the history
Release 2.2
  • Loading branch information
Piszmog authored Jul 29, 2018
2 parents 8013b0c + e671964 commit 062cffc
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 33 deletions.
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

<groupId>io.github.piszmog</groupId>
<artifactId>cloud-config-client-autoconfig</artifactId>
<version>2.1</version>
<version>2.2</version>
<packaging>jar</packaging>

<!--====================================-->
Expand Down Expand Up @@ -38,7 +38,7 @@
<spring-cloud-services-dependencies.version>2.0.1.RELEASE</spring-cloud-services-dependencies.version>
<spring-boot-dependencies.version>2.0.3.RELEASE</spring-boot-dependencies.version>
<!--Dependency-->
<cloud-config-client.version>2.2</cloud-config-client.version>
<cloud-config-client.version>2.3</cloud-config-client.version>
<jackson-dataformat.version>2.9.6</jackson-dataformat.version>
<spring-context.version>5.0.7.RELEASE</spring-context.version>
<spring-security-oauth2.version>2.3.3.RELEASE</spring-security-oauth2.version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import io.github.piszmog.cloudconfigclient.autoconfig.env.model.Resource;
import io.github.piszmog.cloudconfigclient.autoconfig.env.support.MapFlattener;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.env.CompositePropertySource;
Expand All @@ -18,10 +19,12 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

/**
* Property source locator. Locates the JSON files to be loaded as property sources.
Expand All @@ -37,6 +40,7 @@ public class ConfigPropertySourceLocator
// ============================================================

private static final String PROPERTY_SOURCE_NAME = "jsonResource";
private static final String THREAD_NAME = "ConfigLocator";
private static final ObjectMapper YAML_MAPPER = new ObjectMapper( new YAMLFactory() );
private static final JavaPropsMapper PROPERTIES_MAPPER = new JavaPropsMapper();

Expand All @@ -45,7 +49,11 @@ public class ConfigPropertySourceLocator
// ============================================================

private final FileConfigClient fileConfigClient;
private final ExecutorService executorService = Executors.newFixedThreadPool( 10 );
private final ExecutorService executorService = Executors.newFixedThreadPool( 10,
new BasicThreadFactory.Builder().namingPattern( THREAD_NAME + "-%d" )
.daemon( true )
.priority( Thread.MAX_PRIORITY )
.build() );

// ============================================================
// Constructors:
Expand Down Expand Up @@ -73,24 +81,26 @@ public ConfigPropertySourceLocator( final FileConfigClient fileConfigClient )
*/
public PropertySource<?> locateConfigResources( final List<Resource> resources )
{
List<Future<?>> futures = new ArrayList<>();
CompositePropertySource composite = new CompositePropertySource( PROPERTY_SOURCE_NAME );
for ( Resource resource : resources )
{
final String directoryPath = resource.getDirectory();
final List<String> files = resource.getFiles();
getJsonConfiguration( composite, directoryPath, files, futures );
}
//
// Load files asynchronously
//
List<CompletableFuture<Void>> futures = new ArrayList<>();
resources.stream().filter( Objects::nonNull ).map( resource ->
getJsonConfiguration( composite, resource.getDirectory(), resource.getFiles() ) ).forEach( futures::addAll );
final CompletableFuture<List<Void>> listCompletableFuture = CompletableFuture.allOf( futures.toArray( new CompletableFuture[ 0 ] ) )
.thenApply( future -> futures.stream().map( CompletableFuture::join ).collect( Collectors.toList() ) );
try
{
for ( Future<?> future : futures )
{
future.get();
}
//
// Wait for files to complete -- don't want Spring to start setting up Beans without all property sources
//
listCompletableFuture.get();
logger.info( "Successfully loaded all external configuration files." );
}
catch ( InterruptedException | ExecutionException e )
{
throw new ConfigResourceException( "Failed to retrieve file.", e );
throw new ConfigResourceException( "Failed to retrieve files.", e );
}
finally
{
Expand All @@ -103,16 +113,21 @@ public PropertySource<?> locateConfigResources( final List<Resource> resources )
// Private Methods:
// ============================================================

private void getJsonConfiguration( final CompositePropertySource composite, final String directoryPath, final List<String> files, final List<Future<?>> futures )
private List<CompletableFuture<Void>> getJsonConfiguration( final CompositePropertySource composite,
final String directoryPath,
final List<String> files )
{
for ( String fileName : files )
{
futures.add( executorService.submit( () -> loadConfigurationFile( composite, directoryPath, fileName ) ) );
}
return files.stream().filter( Objects::nonNull ).map( file -> CompletableFuture.supplyAsync( () ->
loadConfigurationFile( directoryPath, file ), executorService ).thenAccept( mapPropertySource -> {
if ( mapPropertySource != null )
{
composite.addPropertySource( mapPropertySource );
}
} ) ).collect( Collectors.toList() );
}

@SuppressWarnings( "unchecked" )
private void loadConfigurationFile( final CompositePropertySource composite, final String directoryPath, final String fileName )
private MapPropertySource loadConfigurationFile( final String directoryPath, final String fileName )
{
final String filePath = getFilePath( directoryPath, fileName );
Map file;
Expand All @@ -121,11 +136,11 @@ private void loadConfigurationFile( final CompositePropertySource composite, fin
logger.info( "Loading configuration " + filePath + "..." );
if ( StringUtils.endsWithIgnoreCase( fileName, ".yml" ) || StringUtils.endsWithIgnoreCase( fileName, ".yaml" ) )
{
file = getYAMLFile( directoryPath, fileName, filePath );
file = getYAMLFile( directoryPath, fileName );
}
else if ( StringUtils.endsWithIgnoreCase( fileName, ".properties" ) )
{
file = getPropertiesFile( directoryPath, fileName, filePath );
file = getPropertiesFile( directoryPath, fileName );
}
else
{
Expand All @@ -139,40 +154,41 @@ else if ( StringUtils.endsWithIgnoreCase( fileName, ".properties" ) )
if ( !file.isEmpty() )
{
final Map flattenMap = MapFlattener.flatten( file );
composite.addPropertySource( new MapPropertySource( filePath, flattenMap ) );
return new MapPropertySource( filePath, flattenMap );
}
return null;
}

private String getFilePath( final String directoryPath, final String fileName )
{
return directoryPath + "/" + fileName;
}

private Map getYAMLFile( final String directoryPath, final String fileName, final String filePath ) throws ConfigException
private Map getYAMLFile( final String directoryPath, final String fileName ) throws ConfigException
{
final Map file;
final String yamlFile = fileConfigClient.getFileFromDefaultBranch( fileName, directoryPath, String.class );
final byte[] yamlFile = fileConfigClient.getFileFromDefaultBranch( fileName, directoryPath, byte[].class );
try
{
file = YAML_MAPPER.readValue( yamlFile, Map.class );
}
catch ( IOException e )
{
throw new ConfigResourceException( "Failed to convert " + filePath + " from YAML format to be loaded in the property sources.", e );
throw new ConfigResourceException( "Failed to convert " + fileName + " from YAML format to be loaded in the property sources.", e );
}
return file;
}

private Map getPropertiesFile( final String directoryPath, final String fileName, final String filePath ) throws ConfigException
private Map getPropertiesFile( final String directoryPath, final String fileName ) throws ConfigException
{
final Map file;
try
{
file = PROPERTIES_MAPPER.readValue( fileConfigClient.getFileFromDefaultBranch( fileName, directoryPath, String.class ), Map.class );
file = PROPERTIES_MAPPER.readValue( fileConfigClient.getFileFromDefaultBranch( fileName, directoryPath, byte[].class ), Map.class );
}
catch ( IOException e )
{
throw new ConfigResourceException( "Failed to convert " + filePath + " from PROPERTIES format to be loaded in the property sources.", e );
throw new ConfigResourceException( "Failed to convert " + fileName + " from PROPERTIES format to be loaded in the property sources.", e );
}
return file;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.StreamSupport;

/**
* If enabled and a list of resources to load exist, then the configuration resources are added as property sources.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,13 @@ private static void flatten( String prefix,
map.forEach( ( key, value ) -> {
String finalKey = key;
//
// If key has certain character in it, we want to wrap key in '[' and ']'.
// If key has certain characters in it, we want to wrap key in '[' and ']'.
//
if ( StringUtils.contains( key, ":" )
|| StringUtils.contains( key, "." )
|| StringUtils.contains( key, "*" ) )
|| StringUtils.contains( key, "*" )
|| StringUtils.startsWith( key, "$" )
|| StringUtils.startsWith( key, "#" ) )
{
finalKey = "[" + key + "]";
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package io.github.piszmog.cloudconfigclient.autoconfig.env.support


import spock.lang.Specification

/**
* Created by Piszmog on 7/29/2018
*/
class MapFlattenerSpec extends Specification
{
// ============================================================
// Tests:
// ============================================================

def "flatten a simple map"()
{
given: "a map"
Map topLevelMap = new HashMap()
topLevelMap.put( "key1", "value1" )
topLevelMap.put( "key2", "value2" )

when: "the map is flattened"
def flattenedMap = MapFlattener.flatten( topLevelMap )

then: "the map is flattened"
flattenedMap.get( "key1" ) == "value1"
flattenedMap.get( "key2" ) == "value2"
}

def "flatten a nested map"()
{
given: "a map"
Map level2Map = new HashMap()
level2Map.put( "level2Key1", "value1" )
level2Map.put( "level2Key2", "value2" )
Map level1Map = new HashMap()
level1Map.put( "level2", level2Map )
Map topLevelMap = new HashMap()
topLevelMap.put( "level1", level1Map )

when: "the map is flattened"
def flattenMap = MapFlattener.flatten( topLevelMap )

then: "the map is flattened"
flattenMap.get( "level1.level2.level2Key1" ) == "value1"
flattenMap.get( "level1.level2.level2Key2" ) == "value2"
}

def "flatten a nested map with a list"()
{
given: "a map"
List list = new LinkedList()
list.add( "value1" )
list.add( "value2" )
Map level1Map = new HashMap()
level1Map.put( "list", list )
Map topLevelMap = new HashMap()
topLevelMap.put( "level1", level1Map )

when: "the map is flattened"
def flattenMap = MapFlattener.flatten( topLevelMap )

then: "the map is flattened"
flattenMap.get( "level1.list[0]" ) == "value1"
flattenMap.get( "level1.list[1]" ) == "value2"
}

def "flatten a nested map with special characters"()
{
given: "a map"
Map level2Map = new HashMap()
level2Map.put( "level2.Key1", "value1" )
level2Map.put( "level2:Key2", "value2" )
level2Map.put( "level2*Key3", "value3" )
level2Map.put( "\$level2Key4", "value4" )
level2Map.put( "#level2Key5", "value5" )
level2Map.put( "%level2Key6", "value6" )
level2Map.put( "!level2Key7", "value7" )
level2Map.put( "^level2Key8", "value8" )
level2Map.put( "@level2Key9", "value9" )
level2Map.put( "&level2Key10", "value10" )
Map level1Map = new HashMap()
level1Map.put( "level2", level2Map )
Map topLevelMap = new HashMap()
topLevelMap.put( "level1", level1Map )

when: "the map is flattened"
def flattenMap = MapFlattener.flatten( topLevelMap )

then: "the map is flattened"
flattenMap.get( "level1.level2.[level2.Key1]" ) == "value1"
flattenMap.get( "level1.level2.[level2:Key2]" ) == "value2"
flattenMap.get( "level1.level2.[level2*Key3]" ) == "value3"
flattenMap.get( "level1.level2.[\$level2Key4]" ) == "value4"
flattenMap.get( "level1.level2.[#level2Key5]" ) == "value5"
flattenMap.get( "level1.level2.%level2Key6" ) == "value6"
flattenMap.get( "level1.level2.!level2Key7" ) == "value7"
flattenMap.get( "level1.level2.^level2Key8" ) == "value8"
flattenMap.get( "level1.level2.@level2Key9" ) == "value9"
flattenMap.get( "level1.level2.&level2Key10" ) == "value10"
}
}

0 comments on commit 062cffc

Please sign in to comment.