How to manage your configuration file with YAML in Java programmatically 
Tags: java, yaml, yml, configuration
Published: 2020-12-09 02:53:01

## Introduction

YAML is most useful data format to describe structural hierarchy in these days. It's more simple and collision resistant.
It is useful to describe your configuration attributes in your developments.
We are going to learn to manage configuration YAML in Java programmatically.
For our goal, we will use SnakeYAML library.

OK, Let's dive in.  

## Set up environment
1. At first We need to choose development tool. Even if any development IDE for Java could be used, but I would use Visual Studio Code that have been hotted on these days.  
2. Setting dependency to build.gradle file. if you do like below, It's would be configured classpath automatically.  
```gradle
dependencies {
    // Use JUnit test framework
    testImplementation 'junit:junit:4.12'

	// https://mvnrepository.com/artifact/org.yaml/snakeyaml
	compile group: 'org.yaml', name: 'snakeyaml', version: '1.27'
}
```
3. All of environment setting is over doing this.

## Let's go to code
First, we need to define configuration on config.yml file formatted with YAML.
```gradle
adminPassword: 1234
adminPort: 9292
adminUser: admin
forbiddenRemote: []
host: localhost
sessions:
  Oracle:
    allowedHosts: [127.0.0.1]
    connectionTimeout: 3
    remoteHosts: [192.168.1.152:1521]
    retry: 3
  Kafka:
    allowedHosts: [127.0.0.1]
    connectionTimeout: 3
    remoteHosts: [192.168.1.153:2181]
    retry: 3
```

Second, we have to write Java Bean code mapping with config.yml fields. Top object of configuration is implemented to Config.java.

```java
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Config {
	private String adminPassword;
	private String adminUser;
	private String host;
	private int adminPort;
	private List<String> forbiddenRemote = new ArrayList<>();
	private Map<String, Sessions> sessions = new HashMap<>();
	
	Config() {}
	
	public String getAdminUser() {
		return adminUser;
	}
	public void setAdminUser(String adminUser) {
		this.adminUser = adminUser;
	}
	public String getAdminPassword() {
		return adminPassword;
	}
	public void setAdminPassword(String adminPassword) {
		this.adminPassword = adminPassword;
	}
	public int getAdminPort() {
		return adminPort;
	}
	public void setAdminPort(int adminPort) {
		this.adminPort = adminPort;
	}
	public String getHost() {
		return this.host;
	}
	public void setHost(String host) {
		this.host = host;
	}
	public List<String> getForbiddenRemote() {
		return forbiddenRemote;
	}
	public void setForbiddenRemote(List<String> forbiddenRemote) {
		this.forbiddenRemote = forbiddenRemote;
	}
	public  Map<String, Sessions> getSessions() {
		return sessions;
	}
	public void setSessions(Map<String, Sessions> sessions) {
		this.sessions = sessions;
	}
	public Sessions getSessions(String sessionName) {
		return this.sessions.get(sessionName);
	}

	@Override
	public String toString() {
		return "Config [adminUser=" + adminUser + ", adminPassword=" + adminPassword + ", host=" + host
				+ ", adminPort=" + adminPort + ", forbiddenRemote=" + forbiddenRemote + ", sessions="
				+ sessions + "]";
	}
}
```

Sub classification of Config object is Sessions object. It will be placed it's own to Config object's sessions field as Map element.

```java
import java.util.List;

public class Sessions {
    List<String> allowedHosts;
    int connectionTimeout;
    List<String> remoteHosts;
    int retry;
    
    public Sessions() {
    }
        public List<String> getAllowedHosts() {
		return allowedHosts;
	}

	public void setAllowedHosts(List<String> allowedHosts) {
		this.allowedHosts = allowedHosts;
	}

	public int getConnectionTimeout() {
		return connectionTimeout;
	}

	public void setConnectionTimeout(int connectionTimeout) {
		this.connectionTimeout = connectionTimeout;
	}

	public List<String> getRemoteHosts() {
		return remoteHosts;
	}

	public void setRemoteHosts(List<String> remoteHosts) {
		this.remoteHosts = remoteHosts;
	}

	public int getRetry() {
		return retry;
	}

	public void setRetry(int retry) {
		this.retry = retry;
	}

	@Override
	public String toString() {
		return "Sessions [allowedHosts=" + allowedHosts + ", connectionTimeout=" + connectionTimeout + ", remoteHosts="
				+ remoteHosts + ", retry=" + retry + "]";
	}
}
```

Now, we need to write Handler source file to load config.yml's contents to Java Beans objects we wrote before. To do this, we have to use SnakeYAML library and it's example down below.
```java
Path configPath = Paths.get("./config.yml");
//At first, construct Constructor object using Config.class root object of contents.
Constructor constructor = new Constructor(Config.class);
//Construct Yaml object with constructor object.
Yaml yaml = new Yaml(constructor);
//And then load by given Stream object specified of config.yml file.
yaml.load(new FileInputStream(configPath.toFile()));
```

Also, If we want to dump contents of Config object in memory to file, we should write dumping code like below.

```java
Path configPath = Paths.get("./config.yml");
DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(FlowStyle.BLOCK);
options.setPrettyFlow(true);		
Yaml yml = new Yaml(options);
yml.dump(this.config, new FileWriter(configPath.toFile()));
```

As setting detail options of DumperOptions object, we can handle YAML content for what we want.

We could write Handler to improve for our purpose, then will go to complete Handler code to ConfigHandler.java.
ConfigHandler object will be Singleton style for calling wherever we want in our application. and will have functionality of loading, dumping and referring Config object.
Complete ConfigHandler.java code is down below.

```java
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.DumperOptions.FlowStyle;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;

public class ConfigHandler {
	
	public static final Path configPath = Paths.get("./config.yml");

	private static ConfigHandler configHandler;
	
	Config config;
	
	/**
	 * Get instance of ConfigHandler
	 * @return
	 * @throws FileNotFoundException 
	 */
	public static ConfigHandler getInstance() throws FileNotFoundException {
		return getInstance(configPath);
	}
	
	/**
	 * Get instance of ConfigHandler
	 * @param configPath
	 * @return
	 * @throws FileNotFoundException
	 */
	public static ConfigHandler getInstance(Path configPath) throws FileNotFoundException {
		if(configHandler == null) {
			configHandler = new ConfigHandler(configPath);
		}
		return configHandler;
	}
	
	/**
	 * Constructor
	 * @param configPath
	 * @throws FileNotFoundException 
	 */
	private ConfigHandler(Path configPath) throws FileNotFoundException {
		this.config = loadConfig(configPath);		
	}
	
	/**
	 * Load config.yml
	 * @param configPath
	 * @throws FileNotFoundException
	 */
	public Config loadConfig(Path configPath) throws FileNotFoundException {
		Constructor constructor = new Constructor(Config.class);
		Yaml yaml = new Yaml(constructor);
		return yaml.load(new FileInputStream(configPath.toFile()));
	}
	
	/**
	 * Dump config to config.yml
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws IOException
	 */
	public void dumpConfig() throws IllegalArgumentException, IllegalAccessException, IOException {
		dumpConfig(this.config, this.configPath);
	}
	
	/**
	 * Dump config to config.yml
	 * @param configPath
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws IOException
	 */
	public void dumpConfig(Config config, Path configPath) throws IllegalArgumentException, IllegalAccessException, IOException {
		DumperOptions options = new DumperOptions();
		options.setDefaultFlowStyle(FlowStyle.BLOCK);
		options.setPrettyFlow(true);		
		Yaml yml = new Yaml(options);
		yml.dump(config, new FileWriter(configPath.toFile()));
	}
	
	/**
	 * Get config object
	 * @return
	 */
	public Config getConfig() {
		return this.config;
	}	
	
	/**
	 * Get session mapping object by session name
	 * @param sessionName
	 * @return
	 */
	public Sessions getSessions(String sessionName) {
		return this.config.getSessions().get(sessionName);
	}
	
	public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException, IOException, NoSuchFieldException, SecurityException {
		ConfigHandler handler = ConfigHandler.getInstance();
		Config config = handler.getConfig();
		System.out.println("ADMIN: "+config.getAdminUser());
		System.out.println("PASSWD: "+config.getAdminPassword());
		System.out.println("ORACLE: "+config.getSessions("Oracle").toString());		
		config.setAdminPassword("123456789");
		handler.dumpConfig();
	}
}
```

## Conclusion
YAML is most simple and very fine format to manage contents. 
Easy readability and Structural expression is proving why it's famous in these days. 
For the more, we could use this format to generate huge data files of Java Bean data for Business or Analysis on Big-Data Store.
All of elastic thinking is yours. 

Thanks.