/**
* Copyright (c) 2005-2007, Paul Tuckey
* All rights reserved.
* ====================================================================
* Licensed under the BSD License. Text as follows.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* - Neither the name tuckey.org nor the names of its contributors
* may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* ====================================================================
*/
package org.mobicents.tools.http.urlrewriting;
import org.tuckey.web.filters.urlrewrite.CatchElem;
import org.tuckey.web.filters.urlrewrite.ClassRule;
import org.tuckey.web.filters.urlrewrite.Condition;
import org.tuckey.web.filters.urlrewrite.Conf;
import org.tuckey.web.filters.urlrewrite.NormalRule;
import org.tuckey.web.filters.urlrewrite.OutboundRule;
import org.tuckey.web.filters.urlrewrite.Rule;
import org.tuckey.web.filters.urlrewrite.RuleBase;
import org.tuckey.web.filters.urlrewrite.Run;
import org.tuckey.web.filters.urlrewrite.Runnable;
import org.tuckey.web.filters.urlrewrite.SetAttribute;
import org.tuckey.web.filters.urlrewrite.gzip.GzipFilter;
import org.tuckey.web.filters.urlrewrite.utils.Log;
import org.tuckey.web.filters.urlrewrite.utils.ModRewriteConfLoader;
import org.tuckey.web.filters.urlrewrite.utils.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import javax.servlet.ServletContext;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* Configuration object for urlrewrite filter.
*
* @author Paul Tuckey
* @author Konstantin Nosach (kostyantyn.nosach@telestax.com)
*/
public class BalancerConf extends Conf{
private static Log log = Log.getLog(BalancerConf.class);
private final List errors = new ArrayList();
private final List rules = new ArrayList(50);
private final List catchElems = new ArrayList(10);
private List outboundRules = new ArrayList(50);
private boolean ok = false;
private Date loadedDate = null;
private int ruleIdCounter = 0;
private int outboundRuleIdCounter = 0;
protected boolean useQueryString;
protected boolean useContext;
private static final String NONE_DECODE_USING = "null";
private static final String HEADER_DECODE_USING = "header";
private static final String DEFAULT_DECODE_USING = "header,utf-8";
protected String decodeUsing = DEFAULT_DECODE_USING;
private boolean decodeUsingEncodingHeader;
protected String defaultMatchType = null;
private ServletContext context;
private boolean docProcessed = false;
private boolean engineEnabled = true;
public BalancerConf(Document doc) {
processConfDoc(doc);
if (docProcessed) initialise();
loadedDate = new Date();
}
protected void loadModRewriteStyle(InputStream inputStream) {
ModRewriteConfLoader loader = new ModRewriteConfLoader();
try {
loader.process(inputStream, this);
docProcessed = true;
} catch (IOException e) {
addError("Exception loading conf " + " " + e.getMessage(), e);
}
}
/**
* Process dom document and populate Conf object.
* <p/>
* Note, protected so that is can be extended.
*/
protected synchronized void processConfDoc(Document doc) {
Element rootElement = doc.getDocumentElement();
if ("true".equalsIgnoreCase(getAttrValue(rootElement, "use-query-string"))) setUseQueryString(true);
if ("true".equalsIgnoreCase(getAttrValue(rootElement, "use-context"))) {
log.debug("use-context set to true");
setUseContext(true);
}
setDecodeUsing(getAttrValue(rootElement, "decode-using"));
setDefaultMatchType(getAttrValue(rootElement, "default-match-type"));
NodeList rootElementList = rootElement.getChildNodes();
for (int i = 0; i < rootElementList.getLength(); i++) {
Node node = rootElementList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE &&
((Element) node).getTagName().equals("rule")) {
Element ruleElement = (Element) node;
// we have a rule node
NormalRule rule = new NormalRule();
processRuleBasics(ruleElement, rule);
procesConditions(ruleElement, rule);
processRuns(ruleElement, rule);
Node toNode = ruleElement.getElementsByTagName("to").item(0);
rule.setTo(getNodeValue(toNode));
rule.setToType(getAttrValue(toNode, "type"));
rule.setToContextStr(getAttrValue(toNode, "context"));
rule.setToLast(getAttrValue(toNode, "last"));
rule.setQueryStringAppend(getAttrValue(toNode, "qsappend"));
if ("true".equalsIgnoreCase(getAttrValue(toNode, "encode"))) rule.setEncodeToUrl(true);
processSetAttributes(ruleElement, rule);
addRule(rule);
} else if (node.getNodeType() == Node.ELEMENT_NODE &&
((Element) node).getTagName().equals("class-rule")) {
Element ruleElement = (Element) node;
ClassRule classRule = new ClassRule();
if ("false".equalsIgnoreCase(getAttrValue(ruleElement, "enabled"))) classRule.setEnabled(false);
if ("false".equalsIgnoreCase(getAttrValue(ruleElement, "last"))) classRule.setLast(false);
classRule.setClassStr(getAttrValue(ruleElement, "class"));
classRule.setMethodStr(getAttrValue(ruleElement, "method"));
addRule(classRule);
} else if (node.getNodeType() == Node.ELEMENT_NODE &&
((Element) node).getTagName().equals("outbound-rule")) {
Element ruleElement = (Element) node;
// we have a rule node
OutboundRule rule = new OutboundRule();
processRuleBasics(ruleElement, rule);
if ("true".equalsIgnoreCase(getAttrValue(ruleElement, "encodefirst"))) rule.setEncodeFirst(true);
procesConditions(ruleElement, rule);
processRuns(ruleElement, rule);
Node toNode = ruleElement.getElementsByTagName("to").item(0);
rule.setTo(getNodeValue(toNode));
rule.setToLast(getAttrValue(toNode, "last"));
if ("false".equalsIgnoreCase(getAttrValue(toNode, "encode"))) rule.setEncodeToUrl(false);
processSetAttributes(ruleElement, rule);
addOutboundRule(rule);
} else if (node.getNodeType() == Node.ELEMENT_NODE &&
((Element) node).getTagName().equals("catch")) {
Element catchXMLElement = (Element) node;
// we have a rule node
CatchElem catchElem = new CatchElem();
catchElem.setClassStr(getAttrValue(catchXMLElement, "class"));
processRuns(catchXMLElement, catchElem);
catchElems.add(catchElem);
}
}
docProcessed = true;
}
private void processRuleBasics(Element ruleElement, RuleBase rule) {
if ("false".equalsIgnoreCase(getAttrValue(ruleElement, "enabled"))) rule.setEnabled(false);
String ruleMatchType = getAttrValue(ruleElement, "match-type");
if (StringUtils.isBlank(ruleMatchType)) ruleMatchType = defaultMatchType;
rule.setMatchType(ruleMatchType);
Node nameNode = ruleElement.getElementsByTagName("name").item(0);
rule.setName(getNodeValue(nameNode));
Node noteNode = ruleElement.getElementsByTagName("note").item(0);
rule.setNote(getNodeValue(noteNode));
Node fromNode = ruleElement.getElementsByTagName("from").item(0);
rule.setFrom(getNodeValue(fromNode));
if ("true".equalsIgnoreCase(getAttrValue(fromNode, "casesensitive"))) rule.setFromCaseSensitive(true);
}
private static void processSetAttributes(Element ruleElement, RuleBase rule) {
NodeList setNodes = ruleElement.getElementsByTagName("set");
for (int j = 0; j < setNodes.getLength(); j++) {
Node setNode = setNodes.item(j);
if (setNode == null) continue;
SetAttribute setAttribute = new SetAttribute();
setAttribute.setValue(getNodeValue(setNode));
setAttribute.setType(getAttrValue(setNode, "type"));
setAttribute.setName(getAttrValue(setNode, "name"));
rule.addSetAttribute(setAttribute);
}
}
private static void processRuns(Element ruleElement, Runnable runnable) {
NodeList runNodes = ruleElement.getElementsByTagName("run");
for (int j = 0; j < runNodes.getLength(); j++) {
Node runNode = runNodes.item(j);
if (runNode == null) continue;
Run run = new Run();
processInitParams(runNode, run);
run.setClassStr(getAttrValue(runNode, "class"));
run.setMethodStr(getAttrValue(runNode, "method"));
run.setJsonHandler("true".equalsIgnoreCase(getAttrValue(runNode, "jsonhandler")));
run.setNewEachTime("true".equalsIgnoreCase(getAttrValue(runNode, "neweachtime")));
runnable.addRun(run);
}
// gzip element is just a shortcut to run: org.tuckey.web.filters.urlrewrite.gzip.GzipFilter
NodeList gzipNodes = ruleElement.getElementsByTagName("gzip");
for (int j = 0; j < gzipNodes.getLength(); j++) {
Node runNode = gzipNodes.item(j);
if (runNode == null) continue;
Run run = new Run();
run.setClassStr(GzipFilter.class.getName());
run.setMethodStr("doFilter(ServletRequest, ServletResponse, FilterChain)");
processInitParams(runNode, run);
runnable.addRun(run);
}
}
private static void processInitParams(Node runNode, Run run) {
if (runNode.getNodeType() == Node.ELEMENT_NODE) {
Element runElement = (Element) runNode;
NodeList initParamsNodeList = runElement.getElementsByTagName("init-param");
for (int k = 0; k < initParamsNodeList.getLength(); k++) {
Node initParamNode = initParamsNodeList.item(k);
if (initParamNode == null) continue;
if (initParamNode.getNodeType() != Node.ELEMENT_NODE) continue;
Element initParamElement = (Element) initParamNode;
Node paramNameNode = initParamElement.getElementsByTagName("param-name").item(0);
Node paramValueNode = initParamElement.getElementsByTagName("param-value").item(0);
run.addInitParam(getNodeValue(paramNameNode), getNodeValue(paramValueNode));
}
}
}
private static void procesConditions(Element ruleElement, RuleBase rule) {
NodeList conditionNodes = ruleElement.getElementsByTagName("condition");
for (int j = 0; j < conditionNodes.getLength(); j++) {
Node conditionNode = conditionNodes.item(j);
if (conditionNode == null) continue;
Condition condition = new Condition();
condition.setValue(getNodeValue(conditionNode));
condition.setType(getAttrValue(conditionNode, "type"));
condition.setName(getAttrValue(conditionNode, "name"));
condition.setNext(getAttrValue(conditionNode, "next"));
condition.setCaseSensitive("true".equalsIgnoreCase(getAttrValue(conditionNode, "casesensitive")));
condition.setOperator(getAttrValue(conditionNode, "operator"));
rule.addCondition(condition);
}
}
private static String getNodeValue(Node node) {
if (node == null) return null;
NodeList nodeList = node.getChildNodes();
if (nodeList == null) return null;
Node child = nodeList.item(0);
if (child == null) return null;
if ((child.getNodeType() == Node.TEXT_NODE)) {
String value = ((Text) child).getData();
return value.trim();
}
return null;
}
private static String getAttrValue(Node n, String attrName) {
if (n == null) return null;
NamedNodeMap attrs = n.getAttributes();
if (attrs == null) return null;
Node attr = attrs.getNamedItem(attrName);
if (attr == null) return null;
String val = attr.getNodeValue();
if (val == null) return null;
return val.trim();
}
/**
* Initialise the conf file. This will run initialise on each rule and condition in the conf file.
*/
public void initialise() {
if (log.isDebugEnabled()) {
log.debug("now initialising conf");
}
initDecodeUsing(decodeUsing);
boolean rulesOk = true;
for (int i = 0; i < rules.size(); i++) {
final Rule rule = (Rule) rules.get(i);
if (!rule.initialise(context)) {
// if we failed to initialise anything set the status to bad
rulesOk = false;
}
}
for (int i = 0; i < outboundRules.size(); i++) {
final OutboundRule outboundRule = (OutboundRule) outboundRules.get(i);
if (!outboundRule.initialise(context)) {
// if we failed to initialise anything set the status to bad
rulesOk = false;
}
}
for (int i = 0; i < catchElems.size(); i++) {
final CatchElem catchElem = (CatchElem) catchElems.get(i);
if (!catchElem.initialise(context)) {
// if we failed to initialise anything set the status to bad
rulesOk = false;
}
}
if (rulesOk) {
ok = true;
}
if (log.isDebugEnabled()) {
log.debug("conf status " + ok);
}
}
private void initDecodeUsing(String decodeUsingSetting) {
decodeUsingSetting = StringUtils.trimToNull(decodeUsingSetting);
if (decodeUsingSetting == null) decodeUsingSetting = DEFAULT_DECODE_USING;
if ( decodeUsingSetting.equalsIgnoreCase(HEADER_DECODE_USING)) { // is 'header'
decodeUsingEncodingHeader = true;
decodeUsingSetting = null;
} else if ( decodeUsingSetting.startsWith(HEADER_DECODE_USING + ",")) { // is 'header,xxx'
decodeUsingEncodingHeader = true;
decodeUsingSetting = decodeUsingSetting.substring((HEADER_DECODE_USING + ",").length());
}
if (NONE_DECODE_USING.equalsIgnoreCase(decodeUsingSetting)) {
decodeUsingSetting = null;
}
if ( decodeUsingSetting != null ) {
try {
URLDecoder.decode("testUrl", decodeUsingSetting);
this.decodeUsing = decodeUsingSetting;
} catch (UnsupportedEncodingException e) {
addError("unsupported 'decodeusing' " + decodeUsingSetting + " see Java SDK docs for supported encodings");
}
} else {
this.decodeUsing = null;
}
}
/**
* Destory the conf gracefully.
*/
public void destroy() {
for (int i = 0; i < rules.size(); i++) {
final Rule rule = (Rule) rules.get(i);
rule.destroy();
}
}
/**
* Will add the rule to the rules list.
*
* @param rule The Rule to add
*/
public void addRule(final Rule rule) {
rule.setId(ruleIdCounter++);
rules.add(rule);
}
/**
* Will add the rule to the rules list.
*
* @param outboundRule The outbound rule to add
*/
public void addOutboundRule(final OutboundRule outboundRule) {
outboundRule.setId(outboundRuleIdCounter++);
outboundRules.add(outboundRule);
}
/**
* Will get the List of errors.
*
* @return the List of errors
*/
public List getErrors() {
return errors;
}
/**
* Will get the List of rules.
*
* @return the List of rules
*/
public List getRules() {
return rules;
}
/**
* Will get the List of outbound rules.
*
* @return the List of outbound rules
*/
public List getOutboundRules() {
return outboundRules;
}
/**
* true if the conf has been loaded ok.
*
* @return boolean
*/
public boolean isOk() {
return ok;
}
private void addError(final String errorMsg, final Exception e) {
errors.add(errorMsg);
log.error(errorMsg, e);
}
private void addError(final String errorMsg) {
errors.add(errorMsg);
}
public Date getLoadedDate() {
return (Date) loadedDate.clone();
}
public boolean isUseQueryString() {
return useQueryString;
}
public void setUseQueryString(boolean useQueryString) {
this.useQueryString = useQueryString;
}
public boolean isUseContext() {
return useContext;
}
public void setUseContext(boolean useContext) {
this.useContext = useContext;
}
public String getDecodeUsing() {
return decodeUsing;
}
public void setDecodeUsing(String decodeUsing) {
this.decodeUsing = decodeUsing;
}
public void setDefaultMatchType(String defaultMatchType) {
if (RuleBase.MATCH_TYPE_WILDCARD.equalsIgnoreCase(defaultMatchType)) {
this.defaultMatchType = RuleBase.MATCH_TYPE_WILDCARD;
} else {
this.defaultMatchType = RuleBase.DEFAULT_MATCH_TYPE;
}
}
public String getDefaultMatchType() {
return defaultMatchType;
}
public List getCatchElems() {
return catchElems;
}
public boolean isDecodeUsingCustomCharsetRequired() {
return decodeUsing != null;
}
public boolean isEngineEnabled() {
return engineEnabled;
}
public void setEngineEnabled(boolean engineEnabled) {
this.engineEnabled = engineEnabled;
}
public boolean isDecodeUsingEncodingHeader() {
return decodeUsingEncodingHeader;
}
}