org.rhq.plugins.apache.util.RuntimeApacheConfiguration Maven / Gradle / Ivy
Show all versions of rhq-apache-plugin Show documentation
/*
* 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.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.augeas.node.AugeasNode;
import org.rhq.augeas.tree.AugeasTree;
import org.rhq.core.domain.util.OSGiVersionComparator;
import org.rhq.core.system.ProcessInfo;
import org.rhq.plugins.apache.parser.ApacheDirective;
import org.rhq.plugins.apache.parser.ApacheDirectiveTree;
/**
* @author Lukas Krejci
*/
public class RuntimeApacheConfiguration {
private static final Log LOG = LogFactory.getLog(RuntimeApacheConfiguration.class);
private static final Set LOGGED_UNKNOWN_MODULES = Collections.synchronizedSet(new HashSet());
private enum ModuleLoadedState {
LOADED,
NOT_LOADED,
UNKNOWN
}
private RuntimeApacheConfiguration() {
}
/**
* A result of a node inspection using {@link NodeInspector}
*
*
* @author Lukas Krejci
*/
public static class NodeInspectionResult {
public boolean nodeIsConditional;
public boolean shouldRecurseIntoConditionalNode;
}
/**
* Node inspector is used to determine how to proceed with the parsing of the configuration file.
*
*
* @author Lukas Krejci
*/
public static class NodeInspector {
private TransformState state;
final public boolean keepConditional;
private NodeInspector(TransformState state, boolean keepConditional) {
this.state = state;
this.keepConditional = keepConditional;
}
/**
* Inspects a node.
*
* @param currentNodeName the name of the node
* @param allValues the list of all values specified on the node
* @param valueAsString the original value as a string (from which the list of values was somehow produced)
* @return the inspection result or null if there was some unexpected event (which has been logged)
*/
public NodeInspectionResult inspect(String currentNodeName, List allValues, String valueAsString) {
NodeInspectionResult result = new NodeInspectionResult();
result.shouldRecurseIntoConditionalNode = true;
if (currentNodeName.equalsIgnoreCase("LoadModule")) {
state.loadedModules.add(allValues.get(0));
} else if (currentNodeName.equalsIgnoreCase(" ...
//operator: =, ==, >, >=, <, <=, ~
//version major[.minor[.patch]] or /regex/
//if operator is ~, the version is assumed regex
//if operator is omitted, = is assumed
if (isModuleLoaded("mod_version.c", state.loadedModules, state.moduleNames, state.moduleFiles) != ModuleLoadedState.LOADED) {
LOG.debug("mod_version not loaded and IfVersion directive encountered. Skipping it.");
return null;
}
List values = allValues;
String operator = null;
String version = null;
boolean negate = false;
boolean regex = false;
if (values.size() == 0) {
LOG.warn("Invalid IfVersion directive.");
return null;
}
if (values.size() == 1) {
operator = "=";
version = values.get(0);
} else if (values.size() == 2) {
operator = values.get(0);
version = values.get(1);
} else {
LOG.warn("Too many arguments to a IfVersion directive: " + values);
return null;
}
if (operator == null || version == null) {
LOG.warn("Invalid IfVersion with parameters: " + values);
return null;
}
if (operator.charAt(0) == '!') {
negate = true;
operator = operator.substring(1);
}
if ("==".equals(operator)) {
operator = "=";
}
if (version.charAt(0) == '/') {
if ("=".equals(operator) || "~".equals(operator)) {
regex = true;
version = version.substring(1, version.length() - 1);
} else {
LOG.warn("Unsupported operator " + operator
+ " with regex version comparison in IfVersion directive.");
return null;
}
}
OSGiVersionComparator comp = new OSGiVersionComparator();
boolean hasVersion = false;
if ("=".equals(operator)) {
if (regex) {
hasVersion = Pattern.matches(version, state.httpdVersion);
} else {
hasVersion = comp.compare(version, state.httpdVersion) == 0;
}
} else if ("~".equals(operator)) {
hasVersion = Pattern.matches(version, state.httpdVersion);
} else if (">".equals(operator)) {
hasVersion = comp.compare(state.httpdVersion, version) > 0;
} else if (">=".equals(operator)) {
hasVersion = comp.compare(state.httpdVersion, version) >= 0;
} else if ("<".equals(operator)) {
hasVersion = comp.compare(state.httpdVersion, version) < 0;
} else if ("<=".equals(operator)) {
hasVersion = comp.compare(state.httpdVersion, version) <= 0;
} else {
LOG.warn("Unknown operator " + operator + " in an IfVersion directive.");
return null;
}
result.shouldRecurseIntoConditionalNode = hasVersion != negate;
}
result.nodeIsConditional = ApacheDirective.isConditionalDirectiveName(currentNodeName);
return result;
}
}
/**
* This is a node visitor interface to be implemented by the users of the
* {@link RuntimeApacheConfiguration#walkRuntimeConfig(ApacheAugeasTree, ProcessInfo, ApacheBinaryInfo, Map)}
* or {@link RuntimeApacheConfiguration#walkRuntimeConfig(ApacheDirectiveTree, ProcessInfo, ApacheBinaryInfo, Map)}
* methods.
*/
public interface NodeVisitor {
/**
* This method is called whenever the apache config tree walker encounters one of the If* directives (IfModule, IfDefine, IfVersion).
*
* @param node the If* directive
* @param isSatisfied true if the directive's condition is satisfied, false otherwise
*/
void visitConditionalNode(T node, boolean isSatisfied);
/**
* This method is called for all "ordinary" directives that the apache config tree walker encounters (i.e. all but the ones handled by the {@link #visitConditionalNode(Object)}
* method.
*
* @param node the directive
*/
void visitOrdinaryNode(T node);
}
/**
* Extension of the {@link NodeVisitor} that is used internally to abstract out the
* algorithm from the underlying data model.
* There's just one transform method that walks any kind of apache config tree representation
* and produces the runtime config. Different impls of this interface can
* produce different "side-effects" of that walk.
*
* @author Lukas Krejci
*/
private interface TreeWalker extends NodeVisitor {
void onBeforeChildrenScan(T node);
void onAfterChildrenScan(T node);
Collection getChildren(T node);
String getValue(T node);
List getValues(T node);
String getName(T node);
}
/**
* Impl of {@link TreeWalker} interface that transforms the tree by replacing
* the conditional directives that are satisfied with their "contents".
*
* @author Lukas Krejci
*/
private static class TransformingWalker implements TreeWalker {
private static class NodesToModify {
ArrayList nodesToRemove = new ArrayList();
ArrayList nodesToPromote = new ArrayList();
}
private Deque currentNodeStack = new ArrayDeque();
public void visitConditionalNode(ApacheDirective node, boolean isSatisfied) {
NodesToModify nodes = currentNodeStack.peek();
if (isSatisfied) {
nodes.nodesToPromote.add(node);
} else {
nodes.nodesToRemove.add(node);
}
}
public void visitOrdinaryNode(ApacheDirective node) {
}
public void onBeforeChildrenScan(ApacheDirective node) {
currentNodeStack.push(new NodesToModify());
}
public void onAfterChildrenScan(ApacheDirective parentNode) {
NodesToModify nodes = currentNodeStack.pop();
for (ApacheDirective node : nodes.nodesToRemove) {
parentNode.getChildDirectives().remove(node);
}
//add the children of node as children of parent node at the place node
//was declared and remove node ... i.e. make it so as if the child nodes
//of node were directly in the parentNode in the place of node
for (ApacheDirective node : nodes.nodesToPromote) {
int nodeIdx = parentNode.getChildDirectives().indexOf(node);
List childNodes = node.getChildDirectives();
for (int i = childNodes.size() - 1; i >= 0; --i) {
ApacheDirective childNode = childNodes.get(i);
parentNode.getChildDirectives().add(nodeIdx, childNode);
childNode.setParentNode(parentNode);
}
parentNode.getChildDirectives().remove(nodeIdx + childNodes.size());
}
}
public Collection getChildren(ApacheDirective node) {
return node.getChildDirectives();
}
public String getValue(ApacheDirective node) {
return node.getValuesAsString();
}
public List getValues(ApacheDirective node) {
return node.getValues();
}
public String getName(ApacheDirective node) {
return node.getName();
}
}
/**
* This is a "wrapping" class for the number of parameters that are needed
* in the transform method.
*
* @author Lukas Krejci
*/
private static class TransformState {
public Set loadedModules;
public Set defines;
public Map moduleNames;
public Map moduleFiles;
public String httpdVersion;
public boolean suppressUnknownModuleWarnings;
public TransformState(ProcessInfo httpdProcessInfo, ApacheBinaryInfo httpdBinaryInfo,
Map moduleNames, boolean suppressUnknownModuleWarnings) {
defines = new HashSet(httpdBinaryInfo.getCompiledInDefines());
if (httpdProcessInfo != null) {
String[] args = httpdProcessInfo.getCommandLine();
for (int i = 1; i < args.length; ++i) {
String define = null;
if (args[i] != null && args[i].startsWith("-D")) {
define = args[i].substring(2).trim();
}
if (define != null && define.isEmpty()) {
//this means we saw an empty -D arg. This can happen if there is a space between -D and the value.
//That is legal though, so we have to accomodate for that.
if (i < args.length - 1) {
define = args[i + 1].trim();
if (define.startsWith("-")) {
//this would be another option
define = null;
} else {
++i; //we can skip the next arg
}
} else {
define = null; //well -D is the last argument
}
}
if (define != null) {
defines.add(define);
}
}
}
loadedModules = new HashSet();
loadedModules.addAll(httpdBinaryInfo.getCompiledInModules());
this.moduleNames = moduleNames;
//build a map for reverse lookup we might need in the transform method
moduleFiles = new HashMap(moduleNames.size());
for (Map.Entry e : moduleNames.entrySet()) {
moduleFiles.put(e.getValue(), e.getKey());
}
httpdVersion = httpdBinaryInfo.getVersion();
this.suppressUnknownModuleWarnings = suppressUnknownModuleWarnings;
}
}
public static NodeInspector getNodeInspector(ProcessInfo httpdProcessInfo, ApacheBinaryInfo httpdBinaryInfo,
Map moduleNames, boolean suppressUnknownModuleWarnings, boolean keepConditional) {
return new NodeInspector(new TransformState(httpdProcessInfo, httpdBinaryInfo, moduleNames,
suppressUnknownModuleWarnings), keepConditional);
}
/**
* Given the apache configuration and information about the parameters httpd was executed
* with this method provides the directive tree that corresponds to the actual
* runtime configuration as used by httpd.
*
* This enables us to see which directives are actually in effect as opposed to just
* declared.
*
* @param tree
* @param httpdProcessInfo
* @param httpdBinaryInfo
* @param moduleNames the mapping from the module filename to the module name
* (i.e. mapping from the name used in IfModule to the name used in LoadModule)
* @param suppressUnknownModuleWarnings true if the method should suppress logging the warnings about unknown modules
* @return a new directive tree that represents the runtime configuration
*/
public static ApacheDirectiveTree extract(ApacheDirectiveTree tree, ProcessInfo httpdProcessInfo,
ApacheBinaryInfo httpdBinaryInfo, Map moduleNames, boolean suppressUnknownModuleWarnings) {
ApacheDirectiveTree ret = tree.clone();
transform(new TransformingWalker(), ret.getRootNode(),
getNodeInspector(httpdProcessInfo, httpdBinaryInfo, moduleNames, suppressUnknownModuleWarnings, false));
return ret;
}
public static void walkRuntimeConfig(final NodeVisitor visitor, ApacheDirectiveTree tree,
ProcessInfo httpdProcessInfo, ApacheBinaryInfo httpdBinaryInfo, Map moduleNames,
boolean suppressUnknownModuleWarnings) {
TreeWalker walker = new TreeWalker() {
public void visitConditionalNode(ApacheDirective node, boolean isSatisfied) {
visitor.visitConditionalNode(node, isSatisfied);
}
public void visitOrdinaryNode(ApacheDirective node) {
visitor.visitOrdinaryNode(node);
}
public void onBeforeChildrenScan(ApacheDirective node) {
}
public void onAfterChildrenScan(ApacheDirective node) {
}
public Collection getChildren(ApacheDirective node) {
return node.getChildDirectives();
}
public String getValue(ApacheDirective node) {
return node.getValuesAsString();
}
public List getValues(ApacheDirective node) {
return node.getValues();
}
public String getName(ApacheDirective node) {
return node.getName();
}
};
transform(walker, tree.getRootNode(),
getNodeInspector(httpdProcessInfo, httpdBinaryInfo, moduleNames, suppressUnknownModuleWarnings, false));
}
public static void walkRuntimeConfig(final NodeVisitor visitor, AugeasTree tree,
ProcessInfo httpdProcessInfo, ApacheBinaryInfo httpdBinaryInfo, Map moduleNames,
boolean suppressUnknownModuleWarnings) {
TreeWalker walker = new TreeWalker() {
public void visitConditionalNode(AugeasNode node, boolean isSatisfied) {
visitor.visitConditionalNode(node, isSatisfied);
}
public void visitOrdinaryNode(AugeasNode node) {
visitor.visitOrdinaryNode(node);
}
public void onBeforeChildrenScan(AugeasNode node) {
}
public void onAfterChildrenScan(AugeasNode node) {
}
public Collection getChildren(AugeasNode node) {
return node.getChildNodes();
}
public String getValue(AugeasNode node) {
StringBuilder bld = new StringBuilder();
for (String val : getValues(node)) {
bld.append(val);
}
return bld.toString();
}
public List getValues(AugeasNode node) {
ArrayList ret = new ArrayList();
List params = node.getChildByLabel("param");
for (AugeasNode n : params) {
ret.add(n.getValue());
}
return ret;
}
public String getName(AugeasNode node) {
return node.getLabel();
}
};
transform(walker, tree.getRootNode(),
getNodeInspector(httpdProcessInfo, httpdBinaryInfo, moduleNames, suppressUnknownModuleWarnings, false));
}
private static void transform(TreeWalker walker, T parentNode, NodeInspector inspector) {
if (walker.getChildren(parentNode).isEmpty()) {
return;
}
walker.onBeforeChildrenScan(parentNode);
for (T node : walker.getChildren(parentNode)) {
NodeInspectionResult result =
inspector.inspect(walker.getName(node), walker.getValues(node), walker.getValue(node));
if (result == null) {
continue;
}
if (!result.nodeIsConditional) {
walker.visitOrdinaryNode(node);
} else {
walker.visitConditionalNode(node, result.shouldRecurseIntoConditionalNode);
if (result.shouldRecurseIntoConditionalNode) {
transform(walker, node, inspector);
}
}
}
walker.onAfterChildrenScan(parentNode);
}
private static ModuleLoadedState isModuleLoaded(String moduleIdentifier, Set currentlyLoadedModules,
Map moduleNames, Map moduleFiles) {
String moduleName = moduleNames.get(moduleIdentifier);
if (moduleName == null) {
//as of apache 2.1 module files and module names can both be used in IfModule
moduleName = moduleIdentifier;
moduleIdentifier = moduleFiles.get(moduleName);
if (moduleIdentifier == null) {
//reverse lookup failed - there is no such module in the mappings
//we still have 2 options here.
//If the identifier we were given is a module name, we can assume that
//that module just wasn't loaded if we can't find it in the loaded modules set.
//If on the other hand the identifier is a module source file, we have no other
//option but to give up, because we don't know the mapping from module name to
//module source file and thus cannot determine whether there was a LoadModule
//directive that would load the module.
if (moduleName.endsWith(".c")) {
return ModuleLoadedState.UNKNOWN;
} else {
return currentlyLoadedModules.contains(moduleName) ? ModuleLoadedState.LOADED
: ModuleLoadedState.NOT_LOADED;
}
}
}
//the compiled in modules are being reported by apache using their source file
//and the on-demand loaded modules are identified by their
//module name - consistent, huh?
boolean result =
currentlyLoadedModules.contains(moduleIdentifier) || currentlyLoadedModules.contains(moduleName);
return result ? ModuleLoadedState.LOADED : ModuleLoadedState.NOT_LOADED;
}
}