/*
* RHQ Management Platform
* Copyright (C) 2005-2013 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package org.rhq.plugins.postgres.util;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.rhq.core.util.stream.StreamUtil;
/**
* Represents a PostgreSQL configuration file (i.e. postgresql.conf) - provides methods for reading and updating
* configuration parameters.
*
* @author Greg Hinkle
* @author Ian Springer
*/
public class PostgresqlConfFile {
private Log log = LogFactory.getLog(PostgresqlConfFile.class);
private Map<String, String> properties = new HashMap<String, String>();
private File configurationFile;
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
public PostgresqlConfFile(File configurationFile) throws IOException {
if (!configurationFile.exists()) {
throw new IOException("PostgreSQL configuration file [" + configurationFile
+ "] does not exist or is not readable. Make sure the user the RHQ Agent is running as has read permissions on the file and its parent directory.");
}
if (!configurationFile.canRead()) {
throw new IOException("PostgreSQL configuration file [" + configurationFile
+ "] is not readable. Make sure the user the RHQ Agent is running as has read permissions on the file.");
}
this.configurationFile = configurationFile;
// declared outside the try block, so they can be safely closed in finally block
BufferedReader r = null;
try {
r = new BufferedReader(new FileReader(configurationFile));
Pattern p = Pattern.compile("^" // start match at the beginning of lines
+ "\\s*" // optional whitespace
+ "(\\w+)" // a word, captured
+ "\\s*" // optional whitespace
+ "\\=" // '='
+ "\\s*" // optional whitespace
+ "(" // then capture either
+ "(?:\\w+)" // a word; non-captured, but captured by outer parentheses
+ "|" // or
+ "(?:\\'[^\\']*\\')" // a sequence of non-apostrophe characters surrounded by two apostrophes;
// also non-captured, but captured by outer parenthesis
+ ")" + ".*" // ignored the rest of the line
+ "$"); // end match at the end of lines
String line;
while ((line = r.readLine()) != null) {
Matcher m = p.matcher(line);
if (m.matches()) {
String val = m.group(2);
if (val.startsWith("'") && val.endsWith("'")) {
val = val.substring(1, val.length() - 1);
}
this.properties.put(m.group(1), val);
}
}
} finally {
StreamUtil.safeClose(r);
}
}
@Nullable
public String getProperty(String paramName) {
return this.properties.get(paramName);
}
@NotNull
public List<String> getPropertyList(String propertyName) {
String prop = getProperty(propertyName);
if (prop == null) return Collections.emptyList();
return Arrays.asList(stripQuotes(prop).split(","));
}
@NotNull
public static String stripQuotes(@NotNull String value) {
if (value.startsWith("'") && value.endsWith("'")) {
return value.substring(1, value.length() - 1);
}
return value;
}
@Nullable
public String getPort() {
return getProperty("port");
}
public void setProperties(Map<String, String> parameters) {
// declared outside the try block, so they can be safely closed in finally block
BufferedReader r = null;
BufferedOutputStream bos = null;
try {
r = new BufferedReader(new FileReader(configurationFile));
StringBuilder newBuffer = new StringBuilder();
Pattern p = Pattern.compile("^" // start match at the beginning of lines
+ "\\s*" // optional whitespace
+ "(\\#?)" // an optional '#' sign, signifying a commented line, captured
+ "\\s*" // optional whitespace
+ "(\\w+)" // a word, captured
+ "\\s*" // optional whitespace
+ "\\=" // '='
+ "\\s*" // optional whitespace
+ "(" // then capture either
+ "(?:\\w+)" // a word; non-captured, but captured by outer parentheses
+ "|" // or
+ "(?:\\'[^\\']*\\')" // a sequence of non-apostrophe characters surrounded by two apostrophes;
// also non-captured, but captured by outer parenthesis
+ ")" + "(.*)" // capture the rest of the characters on the line
+ "$"); // end match at the end of lines
String line;
// update existing properties
while ((line = r.readLine()) != null) {
Matcher m = p.matcher(line);
if (m.matches()) {
boolean isCommented = m.group(1).equals("#");
String paramName = m.group(2);
String oldParamValue = m.group(3);
String tail = m.group(4);
if (oldParamValue.startsWith("'") && oldParamValue.endsWith("'")) {
//noinspection UnusedAssignment
oldParamValue = oldParamValue.substring(1, oldParamValue.length() - 1);
}
if (parameters.containsKey(paramName)) {
String paramValue = parameters.get(paramName);
if (paramValue == null) {
// A null value means the parameter should not be specified, so
// comment out the original line if it wasn't already commented out.
if (!isCommented) {
newBuffer.append('#');
}
newBuffer.append(line).append(LINE_SEPARATOR);
} else {
newBuffer.append(paramName).append(" = ").append(paramValue);
if (tail != null) {
newBuffer.append(tail);
}
newBuffer.append(LINE_SEPARATOR);
}
parameters.remove(paramName);
} else {
newBuffer.append(line).append(LINE_SEPARATOR);
}
} else {
newBuffer.append(line).append(LINE_SEPARATOR);
}
}
// add previously non-existent, non-null params at the end
for (String paramName : parameters.keySet()) {
String paramValue = parameters.get(paramName);
if (paramValue != null) {
newBuffer.append(paramName).append(" = ").append(paramValue).append(LINE_SEPARATOR);
}
}
backupFile(configurationFile);
bos = new BufferedOutputStream(new FileOutputStream(configurationFile));
bos.write(newBuffer.toString().getBytes());
} catch (FileNotFoundException e) {
log.warn("Unable to find Postgres configuration file", e);
} catch (IOException e) {
log.warn("Unable to update Postgres configuration file", e);
} finally {
try {
r.close();
bos.close();
} catch (IOException e) {
// ignore
}
}
}
private static void backupFile(File file) throws IOException {
String backupName = file.getName();
File dir = file.getParentFile();
int i = 0;
while (new File(dir, backupName + ".bak." + i).exists()) {
i++;
}
backupName += ".bak." + i;
copyFile(file, new File(dir, backupName));
}
private static void copyFile(File in, File out) throws IOException {
FileInputStream fis = new FileInputStream(in);
FileOutputStream fos = new FileOutputStream(out);
byte[] buf = new byte[1024];
int i;
try {
while ((i = fis.read(buf)) != -1) {
fos.write(buf, 0, i);
}
} finally {
try {
fis.close();
fos.close();
} catch (IOException e) {
// ignore
}
}
}
@Override
public String toString() {
return this.configurationFile.toString();
}
}