/*
* NOTE: This copyright does *not* cover user programs that use HQ
* program services by normal system calls through the application
* program interfaces provided as part of the Hyperic Plug-in Development
* Kit or the Hyperic Client Development Kit - this is merely considered
* normal use of the program, and does *not* fall under the heading of
* "derived work".
*
* Copyright (C) [2004, 2005, 2006], Hyperic, Inc.
* This file is part of HQ.
*
* HQ is free software; you can redistribute it and/or modify
* it under the terms version 2 of the GNU General Public License as
* published by the Free Software Foundation. This program is distributed
* in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA.
*/
package org.hyperic.util;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperic.util.security.SecurityUtil;
import java.io.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class PropertyUtil {
private static final Log LOG = LogFactory.getLog(PropertyUtil.class);
/**
* A regular expression pattern that is used for splitting properties lines into three parts: key, ':' or '=', and
* value. This pattern is used for rewriting properties file (when values need to be encrypted.
*/
private static final Pattern PROPERTY_LINE_PATTERN = Pattern.compile("([^=:]*)([=|:])(.*)");
/**
* Expand variable references in property values.
*
* I.e. if you have a props file:
*
* a=foo
* b=bar
* c=${a} ${b}
*
* The value for 'c' will be 'foo bar'
*
* @param props Properties to replace
*
*/
public static void expandVariables(Map props) {
List<String> vars = new ArrayList<String>();
for (Object o : props.entrySet()) {
Map.Entry ent = (Map.Entry) o;
String value;
int idx;
value = (String) ent.getValue();
idx = value.indexOf("${");
if (idx == -1)
continue;
vars.clear();
while (idx != -1) {
int endIdx = value.indexOf("}", idx);
if (endIdx == -1)
break;
endIdx++;
vars.add(value.substring(idx, endIdx));
idx = value.indexOf("${", endIdx);
}
for (String replace : vars) {
String replaceVar, lookupVal;
replaceVar = replace.substring(2, replace.length() - 1);
lookupVal = (String) props.get(replaceVar);
if (lookupVal == null)
continue;
value = StringUtil.replace(value, replace,
lookupVal);
}
props.put(ent.getKey(), value);
}
}
/**
* Load properties from a file.
*/
public static Properties loadProperties (String file) throws PropertyUtilException {
FileInputStream fi = null;
Properties props = new Properties();
try {
fi = new FileInputStream(file);
props.load(fi);
} catch (Exception exc) {
throw new PropertyUtilException(exc);
} finally {
if (fi != null) try {
fi.close();
} catch (IOException ignore) {
LOG.trace(ignore);
}
}
return props;
}
/**
* encrypt the input entries and append them at the end of the property file.
* Any entries with identical keys would be erased.
*
* @param file the name of the properties file
* @param propEncKey the properties encryption key
* @param entriesToStore a map of properties to store
* @throws PropertyUtilException
*/
public static void storeProperties(String file, String propEncKey, Map<String,String> entriesToStore)
throws PropertyUtilException {
Map<String,String> encryptedEntriesToStore = new HashMap<String,String>();
for (Map.Entry<String, String> entryToStore : entriesToStore.entrySet()) {
String encryptedVal = SecurityUtil.encrypt(
SecurityUtil.DEFAULT_ENCRYPTION_ALGORITHM, propEncKey, entryToStore.getValue());
encryptedEntriesToStore.put(entryToStore.getKey(), encryptedVal);
}
_storeProperties(file,encryptedEntriesToStore);
}
/**
* Saves the provided map of keys and values into the properties file specified by <code>propFilePath</code>.
* Values of properties that already exist int the file are overwritten. New values are placed near the end of the
* file.
*
* @param propFilePath the path (absolute/relative) to the properties file to edit.
* @param props the properties to add/update.
*/
public static void storeProperties(String propFilePath, Map<String, String> props) throws PropertyUtilException {
Map<String,String> duplicatedEntriesToStore = new HashMap<String,String>();
for (Map.Entry<String, String> entryToStore : props.entrySet()) {
duplicatedEntriesToStore.put(entryToStore.getKey(), entryToStore.getValue());
}
_storeProperties(propFilePath, duplicatedEntriesToStore);
}
/**
* Saves the provided map of keys and values into the properties file specified by <code>propFilePath</code>.
* Values of properties that already exist int the file are overwritten. New values are placed near the end of the
* file.
*
* @param propFilePath the path (absolute/relative) to the properties file to edit.
* @param props the properties to add/update.
*/
private static void _storeProperties(String propFilePath, Map<String, String> props) throws PropertyUtilException {
// If the provided properties map is null or empty then exit the method.
if (props == null || props.size() < 1) {
return;
}
// Used for writing the properties file back to the disk.
PrintWriter writer = null;
// Used for reading the properties file from the disk.
FileReader reader = null;
try {
// Create new reader
reader = new FileReader(propFilePath);
// Wrap the reader with a buffer so we can walk through the file line by line.
BufferedReader bufferedReader = new BufferedReader(reader);
// The list of lines that will be written to disk.
List<String> newLines = new ArrayList<String>();
// Read the lines from the file and replace values.
String line;
while ((line = bufferedReader.readLine()) != null) {
newLines.add(processLine(line, props));
}// EO while.
// Iterate values that are left in the provided map and add them to the file.
for (String key : props.keySet()) {
newLines.add(key + " = " + props.get(key));
}
// TODO: change to UTF-8 and add support on property loader side. This might help:
// http://stackoverflow.com/questions/863838/problem-with-java-properties-utf8-encoding-in-eclipse
writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(propFilePath), "ISO-8859-1"));
for (String aLineData : newLines) {
writer.println(aLineData);
}
writer.flush();
} catch (IOException exc) {
String message = "Failed to store properties into the file: " + propFilePath;
LOG.error(message, exc);
throw new PropertyUtilException(exc);
} finally {
if (reader != null) { try { reader.close(); } catch (IOException ignore) { /* ignore */ } }
if (writer != null) { writer.close(); }
} // EO try-catch
} // EOM
/**
* Splits the provided properties line into key, ':' or '=', and value and checks the props map for a new value for
* the extracted key. If there is a new value then the property line is reassembled using the new value; Otherwise
* the line is returned as is.
*
* @param line the property line to process.
* @param props the properties map in which a new value for the property might reside.
* @return the final (modified or same) property line to write in the properties file.
*/
private static String processLine(String line, Map<String, String> props) {
// The result line.
String result;
// User the regex pattern to split the line.
Matcher matcher = PROPERTY_LINE_PATTERN.matcher(line);
// If a match is found then replace it value if there's a matching key in the provided map.
if (matcher.find()) {
// Extract the key (strip the escape characters).
String key = matcher.group(1).trim().replaceAll("\\\\", "");
// Try removing the entry from the map.
String value = props.remove(key);
if (value == null) {
// No matching found. Add the line as is.
result = line;
} else {
// Found a match -- replace the value.
result = matcher.group(1) + matcher.group(2) + value;
}
} else {
// No match found. Add the line as is.
result = line;
}
return result;
}// EOM
}