package org.commons.jconfig.configloader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;
import org.commons.jconfig.internal.jmx.VirtualMachineException;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
/**
* ConfigMerger scans all annotated classes loaded and pulls config resource for
* all those from configLoaderConfig server. It than merges and save merged file
* to filesystem.
*
* It maintains HashMap of each module and its hashcode to identify if the
* configLoaderConfig changed. Scanning for config classes is done only when
* number of jvm process running are changed.
*
* Config Merger merges files with below 2 formats. e.g Input { "Modules": {
* "org.commons.jconfig.ymail.filergateclient.FilerGateClientConfig": {
* "FilerGateTimeout": "500ms", "FilerGateRetries": 1 } } } { "lsgclient": {
* "323": { "lightsaberYCA": "yca.lsg-prod", "lightsaberServer":
* "ls323.com:4080" } } }
*
* @author jaikit
*
*/
public class ConfigMerger {
private final static Logger logger = Logger.getLogger(ConfigMerger.class);
private final ConfigLoaderConfig configLoaderConfig;
private final HttpClient httpClient;
private final JsonParser parser = new JsonParser();
// private int configHashCode = 0;
private final Map<String, Integer> configHashCode = new HashMap<String, Integer>();
public ConfigMerger(ConfigLoaderConfig config) {
if (config == null) {
throw new IllegalArgumentException(
"ConfigLoaderConfig cannot be null");
}
this.configLoaderConfig = config;
httpClient = new DefaultHttpClient();
httpClient.getParams().setParameter("http.socket.timeout",
new Integer(5000));
}
private JsonArray getListOfConfigResources()
throws ClientProtocolException, IOException {
JsonArray listOfConfigResources = new JsonArray();
HttpEntity entity = null;
try {
HttpGet getFiles = new HttpGet(
configLoaderConfig.getConfigServerURL()
+ "config_file_list.json");
logger.info("Fetching config_file_list.json");
HttpResponse response = null;
response = httpClient.execute(getFiles);
entity = response.getEntity();
String jsonString = null;
int statusCode = response.getStatusLine().getStatusCode();
if ((statusCode == 200) && (entity != null)) {
InputStream instream = entity.getContent();
StringWriter writer = new StringWriter();
IOUtils.copy(instream, writer, "UTF-8");
jsonString = writer.toString();
JsonObject localConf = (JsonObject) parser.parse(jsonString);
if (localConf.has("files")) {
listOfConfigResources = localConf.get("files")
.getAsJsonArray();
} else {
throw new IOException(
"Invalid config_file_list.json format.");
}
} else {
throw new IOException("configLoaderConfig file list not found "
+ statusCode);
}
} finally {
EntityUtils.consume(entity);
}
return listOfConfigResources;
}
public void mergeConfig() throws IOException, VirtualMachineException {
if (!configLoaderConfig.getLoadFromServer()) {
loadFromFileSystem();
} else {
loadFromConfigServer();
}
return;
}
private void loadFromConfigServer() throws IOException {
JsonArray configResourceList = getListOfConfigResources();
JsonObject mergedConf = new JsonObject();
JsonObject coater = new JsonObject();
boolean configChange = false;
for (JsonElement c : configResourceList) {
logger.info("Fetching configLoaderConfig file: " + c.toString());
HttpGet httpget = new HttpGet(
configLoaderConfig.getConfigServerURL() + c.getAsString());
HttpResponse response = null;
response = httpClient.execute(httpget);
HttpEntity entity = response.getEntity();
String jsonString = null;
try {
int statusCode = response.getStatusLine().getStatusCode();
if ((statusCode == 200) && (entity != null)) {
InputStream instream = entity.getContent();
StringWriter writer = new StringWriter();
IOUtils.copy(instream, writer, "UTF-8");
jsonString = writer.toString();
JsonElement localConf = parser.parse(jsonString);
if (localConf.isJsonObject()) {
for (Map.Entry<String, JsonElement> applicationConfig : localConf
.getAsJsonObject().entrySet()) {
/*
* configs with Modules { "Modules": {
* "org.commons.jconfig.filergateclient.FilerGateClientConfig"
* : { "FilerGateTimeout": "500ms",
* "FilerGateRetries": 1 } } }
*/
if (applicationConfig.getKey().equals("Modules")) {
JsonElement modules = applicationConfig
.getValue();
for (Map.Entry<String, JsonElement> elem : modules
.getAsJsonObject().entrySet()) {
if (!configHashCode.containsKey(elem
.getKey())
|| (configHashCode.get(elem
.getKey()) != elem
.getValue().toString()
.hashCode())) {
configHashCode.put(elem.getKey(), elem
.getValue().toString()
.hashCode());
configChange = true;
logger.info("Config value changed for module: "
+ elem.getKey()
+ " and configLoaderConfig value is: "
+ jsonString);
}
mergedConf.add(elem.getKey(),
elem.getValue());
}
} else {
/*
* configLoaderConfig without Modules section {
* "lsgclient": { "323": { "lightsaberYCA":
* "org.commons.jconfig.acl.yca.lsg-prod",
* "lightsaberServer":
* "ls323.mail.vip.mud.com:4080" } } }
*/
coater.add(applicationConfig.getKey(),
applicationConfig.getValue());
}
}
} else {
logger.error("Incorrect json format for configLoaderConfig "
+ configLoaderConfig);
}
} else {
logger.error("No response from configLoaderConfig server for configLoaderConfig "
+ c.getAsString()
+ " and the status code is: "
+ response.getStatusLine().getStatusCode());
}
} finally {
EntityUtils.consume(entity);
}
}
coater.add("Modules", mergedConf);
if (configChange) {
saveConfigToFile(coater);
}
}
private void loadFromFileSystem() throws IOException {
JsonObject coater = new JsonObject();
boolean configChange = false;
JsonObject mergedConf = new JsonObject();
JsonArray fileList = getFileList();
for (final JsonElement element : fileList) {
File fileEntry = new File(configLoaderConfig.getConfigPath()
+ element.getAsString());
if (!fileEntry.isDirectory() && fileEntry.canRead()) {
InputStream instream = new FileInputStream(fileEntry);
StringWriter writer = new StringWriter();
IOUtils.copy(instream, writer, "UTF-8");
String jsonString = writer.toString();
JsonElement localConf = parser.parse(jsonString);
if (localConf.isJsonObject()) {
for (Map.Entry<String, JsonElement> applicationConfig : localConf
.getAsJsonObject().entrySet()) {
/*
* configs with Modules { "Modules": {
* "org.commons.jconfig.filergateclient.FilerGateClientConfig"
* : { "FilerGateTimeout": "500ms", "FilerGateRetries":
* 1 } } }
*/
if (applicationConfig.getKey().equals("Modules")) {
JsonElement modules = applicationConfig.getValue();
for (Map.Entry<String, JsonElement> elem : modules
.getAsJsonObject().entrySet()) {
if (!configHashCode.containsKey(elem.getKey())
|| (configHashCode.get(elem.getKey()) != elem
.getValue().toString()
.hashCode())) {
configHashCode.put(elem.getKey(), elem
.getValue().toString().hashCode());
configChange = true;
logger.info("Config value changed for module: "
+ elem.getKey()
+ " and configLoaderConfig value is: "
+ jsonString);
}
mergedConf.add(elem.getKey(), elem.getValue());
}
} else {
/*
* configLoaderConfig without Modules section {
* "lsgclient": { "323": { "lightsaberYCA":
* "org.commons.jconfig.acl.yca.lsg-prod",
* "lightsaberServer": "ls323.mail.vip.mud.com:4080"
* } } }
*/
coater.add(applicationConfig.getKey(),
applicationConfig.getValue());
}
}
} else {
logger.error("Incorrect json format for configLoaderConfig "
+ configLoaderConfig);
}
}
}
coater.add("Modules", mergedConf);
if (configChange) {
saveConfigToFile(coater);
}
}
private JsonArray getFileList() throws IOException {
JsonArray listOfConfigResources = null;
/* read file 'config_file_list.json' and get list of files to be merged. */
File config_file_list = new File(configLoaderConfig.getConfigPath()
+ "config_file_list.json");
if (config_file_list == null || !config_file_list.canRead()) {
throw new IOException(
"Error reading config_file_list file from path "
+ configLoaderConfig.getConfigPath());
}
InputStream fileListStream = new FileInputStream(config_file_list);
StringWriter writer = new StringWriter();
IOUtils.copy(fileListStream, writer, "UTF-8");
String jsonString = writer.toString();
JsonObject localConf = (JsonObject) parser.parse(jsonString);
if (localConf.has("files")) {
listOfConfigResources = localConf.get("files").getAsJsonArray();
} else {
throw new IOException("Invalid config_file_list.json format.");
}
return listOfConfigResources;
}
/**
* @param coater
* @throws IOException
*/
private void saveConfigToFile(JsonObject coater) throws IOException {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String data = gson.toJson(coater);
File currFile = new File(configLoaderConfig.getConfigFileName());
File tmpFile = File.createTempFile("mergedConf.conf", null,
currFile.getParentFile());
Writer writer = new FileWriter(tmpFile);
writer.write(data);
writer.close();
if (currFile.exists()) {
if (currFile.delete()) {
tmpFile.renameTo(currFile);
} else {
logger.error("Error deleting file " + currFile.getName()
+ ". Cannot rename temp file " + tmpFile.getName());
}
} else {
tmpFile.renameTo(currFile);
}
}
}