/*
* RHQ Management Platform
* Copyright (C) 2005-2011 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.plugins.apache.util;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.unitils.thirdparty.org.apache.commons.io.FileUtils;
import org.rhq.core.util.file.FileUtil;
import org.rhq.core.util.stream.StreamUtil;
import org.rhq.plugins.apache.util.HttpdAddressUtility.Address;
import org.rhq.test.PortScout;
import org.rhq.core.util.TokenReplacingProperties;
import org.rhq.core.util.TokenReplacingReader;
/**
*
*
* @author Lukas Krejci
*/
public class ApacheDeploymentUtil {
private static final Log LOG = LogFactory.getLog(ApacheDeploymentUtil.class);
private static final String VHOST = "vhost";
private static final String SERVER_ROOT = "server.root";
private static final String ADDITIONAL_DIRECTIVES = "additional.directives";
private static final String SERVERNAME_DIRECTIVE = "servername.directive";
private static final String SERVERNAME = "servername";
private static final String LISTEN4 = "listen4";
private static final String LISTEN3 = "listen3";
private static final String LISTEN2 = "listen2";
private static final String LISTEN1 = "listen1";
private static final String SNMP_PORT = "snmp.port";
private static final String SNMP_HOST = "snmp.host";
private static final String DOCUMENT_ROOT = "document.root";
private static final String URLS = "urls";
private ApacheDeploymentUtil() {
}
public static class DeploymentConfig {
public static class VHost {
public Address address1 = new Address(null, null, Address.NO_PORT_SPECIFIED_VALUE);
public Address address2 = null;
public Address address3 = null;
public Address address4 = null;
public String serverNameDirective = null;
public final List<String> additionalDirectives = new ArrayList<String>();
private String getServerName() {
String serverName = null;
if (serverNameDirective != null && serverNameDirective.startsWith("ServerName")) {
int startIdx = serverNameDirective.indexOf(' ');
if (startIdx >= 0) {
while (serverNameDirective.charAt(startIdx) == ' ') {
++startIdx;
}
serverName = serverNameDirective.substring(startIdx);
serverName = serverName.trim();
}
}
return serverName;
}
private String getServerName(Map<String, String> replacements) {
String serverName = getServerName();
if (serverName == null) {
return null;
}
TokenReplacingReader rdr = new TokenReplacingReader(new StringReader(getServerName()), replacements);
StringWriter wrt = new StringWriter();
StreamUtil.copy(rdr, wrt);
return wrt.toString();
}
public VHostSpec getVHostSpec(Map<String, String> replacements) {
VHostSpec ret = new VHostSpec();
ret.serverName = getServerName(replacements);
ret.hosts = getAddresses(replacements);
return ret;
}
private List<String> getAddresses() {
ArrayList<String> ret = new ArrayList<String>();
if (address1 != null) {
ret.add(address1.toString());
}
if (address2 != null) {
ret.add(address2.toString());
}
if (address3 != null) {
ret.add(address3.toString());
}
if (address4 != null) {
ret.add(address4.toString());
}
return ret;
}
public List<String> getAddresses(Map<String, String> replacements) {
List<String> addresses = getAddresses();
ListIterator<String> it = addresses.listIterator();
while(it.hasNext()) {
String addr = it.next();
TokenReplacingReader rdr = new TokenReplacingReader(new StringReader(addr), replacements);
StringWriter wrt = new StringWriter();
StreamUtil.copy(rdr, wrt);
it.set(wrt.toString());
}
return addresses;
}
private void addToTokenReplacements(int ordinal, Map<String, String> tokenReplacements) {
String prefix = null;
if (ordinal == 0) {
prefix = "";
} else {
prefix = VHOST + ordinal + ".";
}
tokenReplacements.put(prefix + SERVERNAME_DIRECTIVE, serverNameDirective == null ? ""
: serverNameDirective);
String serverName = getServerName();
tokenReplacements.put(prefix + SERVERNAME, serverName == null ? "" : serverName);
String dirs = "";
if (!additionalDirectives.isEmpty()) {
String newline = System.getProperty("line.separator");
for (String dir : additionalDirectives) {
dirs += dir + newline;
}
}
tokenReplacements.put(prefix + ADDITIONAL_DIRECTIVES, dirs);
if (ordinal != 0) {
String urls = address1.toString(false, false);
if (address2 != null) {
urls += " " + address2.toString(false, false);
}
if (address3 != null) {
urls += " " + address3.toString(false, false);
}
if (address4 != null) {
urls += " " + address4.toString(false, false);
}
tokenReplacements.put(prefix + URLS, urls);
} else {
tokenReplacements.put(LISTEN1, address1 == null ? "" : address1.toString(false, false));
tokenReplacements.put(LISTEN2, address2 == null ? "" : address2.toString(false, false));
tokenReplacements.put(LISTEN3, address3 == null ? "" : address3.toString(false, false));
tokenReplacements.put(LISTEN4, address4 == null ? "" : address4.toString(false, false));
}
}
@Override
public String toString() {
StringBuilder bld = new StringBuilder("VHost[");
if (address1 != null) {
bld.append(address1.toString()).append(", ");
}
if (address2 != null) {
bld.append(address2.toString()).append(", ");
}
if (address3 != null) {
bld.append(address3.toString()).append(", ");
}
if (address4 != null) {
bld.append(address4.toString()).append(", ");
}
String serverName = getServerName();
if (serverName != null) {
bld.append("serverName=").append(serverName).append(", ");
}
if (additionalDirectives != null) {
bld.append("with additional directives, ");
}
bld.replace(bld.length() - 2, bld.length(), "]");
return bld.toString();
}
}
public String serverRoot = null;
public String documentRoot = "htdocs";
public String snmpHost = "localhost";
public int snmpPort = 1610;
public final VHost mainServer = new VHost();
public final VHost vhost1 = new VHost();
public final VHost vhost2 = new VHost();
public final VHost vhost3 = new VHost();
public final VHost vhost4 = new VHost();
{
mainServer.address2 = new Address(null, null, Address.NO_PORT_SPECIFIED_VALUE);
mainServer.address3 = new Address(null, null, Address.NO_PORT_SPECIFIED_VALUE);
mainServer.address4 = new Address(null, null, Address.NO_PORT_SPECIFIED_VALUE);
}
public Map<String, String> getTokenReplacements() {
Map<String, String> ret = new TokenReplacingProperties(new HashMap<String, String>());
ret.put(SERVER_ROOT, serverRoot == null ? "" : serverRoot);
ret.put(DOCUMENT_ROOT, documentRoot == null ? "" : documentRoot);
ret.put(SNMP_HOST, snmpHost == null ? "" : snmpHost);
ret.put(SNMP_PORT, Integer.toString(snmpPort));
mainServer.addToTokenReplacements(0, ret);
vhost1.addToTokenReplacements(1, ret);
vhost2.addToTokenReplacements(2, ret);
vhost3.addToTokenReplacements(3, ret);
vhost4.addToTokenReplacements(4, ret);
return ret;
}
public VHost getVHost(int number) {
switch (number) {
case 0 : return mainServer;
case 1 : return vhost1;
case 2 : return vhost2;
case 3 : return vhost3;
case 4 : return vhost4;
default: throw new IllegalArgumentException();
}
}
@Override
public String toString() {
return new StringBuilder("DeploymentConfig[").append("mainServer=").append(mainServer).append(", vhost1=")
.append(vhost1).append(", vhost2=").append(vhost2).append(", vhost3=").append(vhost3)
.append(", vhost4=").append(vhost4).append(", serverRoot=").append(serverRoot)
.append(", documentRoot=").append(documentRoot).append(", snmpHost=").append(snmpHost)
.append(", snmpPort=").append(snmpPort).append("]").toString();
}
}
public static void addDefaultVariables(Map<String, String> variables, String prefix) {
InetAddress localhost = determineLocalhost();
PortScout portScout = new PortScout();
try {
checkOrAddDefault(variables, "localhost", localhost.getHostName());
checkOrAddDefault(variables, "localhost.ip", localhost.getHostAddress());
checkOrAddDefault(variables, "unresolvable.host", "unreachable.host.com");
checkOrAddDefault(variables, "port1", getRandomFreePort(portScout));
checkOrAddDefault(variables, "port2", getRandomFreePort(portScout));
checkOrAddDefault(variables, "port3", getRandomFreePort(portScout));
checkOrAddDefault(variables, "port4", getRandomFreePort(portScout));
if (prefix != null && !prefix.trim().isEmpty()) {
prefix += ".";
} else {
prefix = "";
}
checkOrAddDefault(variables, prefix + "snmp.port", getRandomFreePort(portScout));
checkOrAddDefault(variables, prefix + "listen1", "${port1}");
checkOrAddDefault(variables, prefix + "listen2", "${port2}");
checkOrAddDefault(variables, prefix + "listen3", "${port3}");
checkOrAddDefault(variables, prefix + "listen4", "${port4}");
checkOrAddDefault(variables, prefix + "vhost1.servername", "${localhost}:${port1}");
checkOrAddDefault(variables, prefix + "vhost1.urls", "${" + prefix + "vhost1.servername}");
checkOrAddDefault(variables, prefix + "vhost1.servername.directive", "ServerName ${" + prefix
+ "vhost1.servername}");
checkOrAddDefault(variables, prefix + "vhost2.servername", "${localhost}:${port2}");
checkOrAddDefault(variables, prefix + "vhost2.urls", "${" + prefix + "vhost2.servername}");
checkOrAddDefault(variables, prefix + "vhost2.servername.directive", "ServerName ${" + prefix
+ "vhost2.servername}");
checkOrAddDefault(variables, prefix + "vhost3.servername", "${localhost}:${port3}");
checkOrAddDefault(variables, prefix + "vhost3.urls", "${" + prefix + "vhost3.servername}");
checkOrAddDefault(variables, prefix + "vhost3.servername.directive", "ServerName ${" + prefix
+ "vhost3.servername}");
checkOrAddDefault(variables, prefix + "vhost4.servername", "${localhost}:${port4}");
checkOrAddDefault(variables, prefix + "vhost4.urls", "${" + prefix + "vhost4.servername}");
checkOrAddDefault(variables, prefix + "vhost4.servername.directive", "ServerName ${" + prefix
+ "vhost4.servername}");
} finally {
try {
portScout.close();
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
private static void checkOrAddDefault(Map<String, String> map, String key, String value) {
if (!map.containsKey(key)) {
map.put(key, value);
}
}
public static DeploymentConfig getDeploymentConfigurationFromSystemProperties(String variablesPrefix, Map<String, String> defaultOverrides) {
DeploymentConfig ret = new DeploymentConfig();
TokenReplacingProperties properties = new TokenReplacingProperties(defaultOverrides);
addDefaultVariables(properties, variablesPrefix);
properties.putAll(System.getProperties());
variablesPrefix += ".";
ret.serverRoot = properties.get(variablesPrefix + SERVER_ROOT);
ret.documentRoot = properties.get(variablesPrefix + DOCUMENT_ROOT);
ret.documentRoot = ret.documentRoot == null ? "htdocs" : ret.documentRoot;
ret.snmpHost = properties.get(variablesPrefix + SNMP_HOST);
ret.snmpHost = ret.snmpHost == null ? "localhost" : ret.snmpHost;
String snmpPort = properties.get(variablesPrefix + SNMP_PORT);
snmpPort = snmpPort == null ? "1610" : snmpPort;
ret.snmpPort = Integer.parseInt(snmpPort);
ret.mainServer.address1 = HttpdAddressUtility.parseListen(properties.get(variablesPrefix + LISTEN1));
ret.mainServer.address2 = HttpdAddressUtility.parseListen(properties.get(variablesPrefix + LISTEN2));
ret.mainServer.address3 = HttpdAddressUtility.parseListen(properties.get(variablesPrefix + LISTEN3));
ret.mainServer.address4 = HttpdAddressUtility.parseListen(properties.get(variablesPrefix + LISTEN4));
ret.mainServer.serverNameDirective = properties.get(variablesPrefix + SERVERNAME_DIRECTIVE);
String additionalDirectives = properties.get(variablesPrefix + ADDITIONAL_DIRECTIVES);
fillAdditionalDirectives(additionalDirectives, ret.mainServer.additionalDirectives);
readVHostConfigFromProperties(ret.vhost1, variablesPrefix + VHOST + 1, properties);
readVHostConfigFromProperties(ret.vhost2, variablesPrefix + VHOST + 2, properties);
readVHostConfigFromProperties(ret.vhost3, variablesPrefix + VHOST + 3, properties);
readVHostConfigFromProperties(ret.vhost4, variablesPrefix + VHOST + 4, properties);
return ret;
}
public static void deployConfiguration(File targetConfDirectory, Collection<String> configFilesOnClasspath,
Collection<File> additionalTargetFiles, DeploymentConfig config) throws IOException {
List<File> targetFiles = new ArrayList<File>(additionalTargetFiles);
for (String fileOnClassPath : configFilesOnClasspath) {
String fileName = new File(fileOnClassPath).getName();
File targetFile = new File(targetConfDirectory, fileName);
URL fileUrl = ApacheDeploymentUtil.class.getResource(fileOnClassPath);
FileUtils.copyURLToFile(fileUrl, targetFile);
targetFiles.add(targetFile);
}
replaceTokensInConfigFiles(targetFiles, config);
}
private static void replaceTokensInConfigFiles(List<File> configFiles, DeploymentConfig config) {
char[] buffer = new char[8192];
Map<String, String> replacements = config.getTokenReplacements();
for (File file : configFiles) {
try {
File tmp = File.createTempFile("apache-deployment-util", null);
FileWriter wrt = new FileWriter(tmp);
try {
Reader rdr = new TokenReplacingReader(new FileReader(file), replacements);
try {
int cnt = -1;
while ((cnt = rdr.read(buffer)) != -1) {
wrt.write(buffer, 0, cnt);
}
} finally {
rdr.close();
}
} finally {
wrt.close();
}
//now overwrite the contents of the original file with the new one.
//we don't just move the new file to the location of the original one
//here to preserve the file permissions and file mode on the original.
FileUtil.copyFile(tmp, file);
} catch (IOException e) {
LOG.error("Error while replacing the tokens in file '" + file + "'.", e);
}
}
}
private static void readVHostConfigFromProperties(DeploymentConfig.VHost vhost, String prefix,
Map<String, String> properties) {
prefix += ".";
String addrsString = properties.get(prefix + URLS);
if (addrsString == null) {
throw new IllegalStateException("The system property '" + prefix
+ "urls' doesn't exist. It is needed to define the vhost.");
}
String[] addrs = addrsString.split("[ \t]+");
//the fallthroughs below are intentional
switch (addrs.length) {
case 4:
vhost.address4 = Address.parse(addrs[3], null);
case 3:
vhost.address3 = Address.parse(addrs[2], null);
case 2:
vhost.address2 = Address.parse(addrs[1], null);
case 1:
vhost.address1 = Address.parse(addrs[0], null);
break;
default:
throw new IllegalStateException("The system property '" + prefix + "urls' specified " + addrs.length
+ " addresses. Only 1-4 addresses are supported.");
}
vhost.serverNameDirective = properties.get(prefix + SERVERNAME_DIRECTIVE);
String additionalDirectives = properties.get(prefix + ADDITIONAL_DIRECTIVES);
fillAdditionalDirectives(additionalDirectives, vhost.additionalDirectives);
}
private static void fillAdditionalDirectives(String additionalDirectivesString, List<String> additionalDirectives) {
if (additionalDirectivesString != null) {
for (String dir : additionalDirectivesString.split("\\n")) {
additionalDirectives.add(dir);
}
}
}
private static InetAddress determineLocalhost() {
try {
return InetAddress.getLocalHost();
} catch (UnknownHostException e) {
try {
return InetAddress.getByName("127.0.0.1");
} catch (UnknownHostException u) {
//doesn't happen
return null;
}
}
};
private static String getRandomFreePort(PortScout scout) throws IllegalStateException {
try {
return Integer.toString(scout.getNextFreePort());
} catch (IOException e) {
throw new IllegalStateException("Failed to find a free port.", e);
}
}
}