XmlConfigurator.java
package com.ziesemer.utils.web.addressFilter;
import java.util.ArrayList;
import java.util.List;
import javax.xml.XMLConstants;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
import com.ziesemer.utils.web.addressFilter.actions.AllowAction;
import com.ziesemer.utils.web.addressFilter.actions.IAction;
import com.ziesemer.utils.web.addressFilter.actions.RejectAction;
import com.ziesemer.utils.web.addressFilter.actions.RequestAttributeAction;
import com.ziesemer.utils.web.addressFilter.conditions.AnyCondition;
import com.ziesemer.utils.web.addressFilter.conditions.HostNameOnDemandAddressChecker;
import com.ziesemer.utils.web.addressFilter.conditions.HostNameStartupAddressChecker;
import com.ziesemer.utils.web.addressFilter.conditions.IAddressChecker;
import com.ziesemer.utils.web.addressFilter.conditions.ICondition;
import com.ziesemer.utils.web.addressFilter.conditions.IPAddressCheckerEmpty;
import com.ziesemer.utils.web.addressFilter.conditions.IPAddressCheckerFactory;
import com.ziesemer.utils.web.addressFilter.conditions.IPPortCheckerFactory;
import com.ziesemer.utils.web.addressFilter.conditions.IPortChecker;
import com.ziesemer.utils.web.addressFilter.conditions.IStringMatcher;
import com.ziesemer.utils.web.addressFilter.conditions.IsSecureCondition;
import com.ziesemer.utils.web.addressFilter.conditions.LocalAddrCondition;
import com.ziesemer.utils.web.addressFilter.conditions.LocalHostOnDemandAddressChecker;
import com.ziesemer.utils.web.addressFilter.conditions.LocalHostStartupAddressChecker;
import com.ziesemer.utils.web.addressFilter.conditions.LocalPortCondition;
import com.ziesemer.utils.web.addressFilter.conditions.RemoteAddrCondition;
import com.ziesemer.utils.web.addressFilter.conditions.RemotePortCondition;
import com.ziesemer.utils.web.addressFilter.conditions.RequestAttributeCondition;
import com.ziesemer.utils.web.addressFilter.conditions.SchemeCondition;
import com.ziesemer.utils.web.addressFilter.conditions.StringMatcherExact;
import com.ziesemer.utils.web.addressFilter.conditions.StringMatcherRegex;
import com.ziesemer.utils.web.addressFilter.conditions.UserInRoleCondition;
import com.ziesemer.utils.web.addressFilter.conditions.XForwardedForCondition;
/**
* @author Mark A. Ziesemer
* <a href="http://www.ziesemer.com."><www.ziesemer.com></a>
*/
public class XmlConfigurator extends DefaultHandler{
protected static final Logger LOGGER = LoggerFactory.getLogger(XmlConfigurator.class);
protected static final String NS = "http://namespaces.ziesemer.com/utils.web/addressFilter";
protected InputSource source;
protected enum State{
DEFAULT, UNKNOWN, OTHER,
ADDRESS_FILTER, ADDRESS_GROUP, DEFAULT_ACTION,
LOCAL_ADDR, LOCAL_PORT, REMOTE_ADDR, REMOTE_PORT,
X_FORWARDED_FOR, IS_SECURE, SCHEME, USER_IN_ROLE, REQUEST_ATTR, ACTION,
IP_ADDRESS, HOST_NAME, LOCALHOST, PORT, PROXY_IDX,
STRING_MATCH, RESOLUTION,
EXACT_MATCH, REGEX_MATCH,
SET_REQUEST_ATTR, REQUEST_ATTR_NAME, REQUEST_ATTR_VALUE;
}
protected enum Resolution{
Startup, OnDemand;
}
protected String filterName;
protected Locator locator;
protected State state = State.DEFAULT;
protected List<State> stateStack = new ArrayList<State>();
protected StringBuilder charBuffer = new StringBuilder();
protected List<AddressGroup> addressGroups;
protected List<IAction> defaultActions;
protected String addressGroupId;
protected List<ICondition> conditions = new ArrayList<ICondition>();
protected List<IAddressChecker> addressCheckers = new ArrayList<IAddressChecker>();
protected List<IPortChecker> portCheckers = new ArrayList<IPortChecker>();
protected List<IAction> groupActions = new ArrayList<IAction>();
protected List<IAction> actions;
protected int proxyIndex;
protected Resolution resolution;
protected IStringMatcher stringMatcher;
protected String requestAttrName;
protected String requestAttrValue;
public XmlConfigurator(final InputSource source){
this.source = source;
}
@Override
public void setDocumentLocator(final Locator locator){
super.setDocumentLocator(locator);
this.locator = locator;
}
public void configure(final String filterName, final List<AddressGroup> groups, final List<IAction> defaultActions) throws Exception{
this.filterName = filterName;
final SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
final Schema schema = sf.newSchema(new StreamSource(XmlConfigurator.class.getResourceAsStream("AddressFilter.xsd")));
final SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setNamespaceAware(true);
spf.setSchema(schema);
this.addressGroups = groups;
this.defaultActions = defaultActions;
final SAXParser sp = spf.newSAXParser();
sp.parse(source, this);
}
@Override
public void warning(final SAXParseException e) throws SAXException{
LOGGER.warn("({}) {} {}", filterName, e, getLocatorFormatter(e), e);
super.warning(e);
}
@Override
public void error(final SAXParseException e) throws SAXException{
LOGGER.error("({}) {} {}", filterName, e, getLocatorFormatter(e), e);
throw e;
}
@Override
public void fatalError(final SAXParseException e) throws SAXException{
LOGGER.error("({}) {} {}", filterName, e, getLocatorFormatter(e), e);
throw e;
}
@Override
public void startElement(final String uri, final String localName, final String qName, final Attributes attributes)
throws SAXException{
stateStack.add(state);
State newState = State.UNKNOWN;
if(NS.equals(uri)){
switch(state){
case DEFAULT: {
if("AddressFilter".equals(localName)){
newState = State.ADDRESS_FILTER;
}
break;
}
case ADDRESS_FILTER: {
if("AddressGroup".equals(localName)){
addressGroupId = attributes.getValue("groupId");
actions = groupActions;
newState = State.ADDRESS_GROUP;
}else if("DefaultAction".equals(localName)){
actions = defaultActions;
newState = State.DEFAULT_ACTION;
}
break;
}
case ADDRESS_GROUP: {
if("LocalAddr".equals(localName)){
newState = State.LOCAL_ADDR;
}else if("LocalPort".equals(localName)){
newState = State.LOCAL_PORT;
}else if("RemoteAddr".equals(localName)){
newState = State.REMOTE_ADDR;
}else if("RemotePort".equals(localName)){
newState = State.REMOTE_PORT;
}else if("X-Forwarded-For".equals(localName)){
newState = State.X_FORWARDED_FOR;
}else if("IsSecure".equals(localName)){
newState = State.IS_SECURE;
}else if("Scheme".equals(localName)){
newState = State.SCHEME;
}else if("UserInRole".equals(localName)){
newState = State.USER_IN_ROLE;
}else if("RequestAttribute".equals(localName)){
newState = State.REQUEST_ATTR;
}else if("Action".equals(localName)){
newState = State.ACTION;
}
break;
}
case LOCAL_ADDR:
case REMOTE_ADDR: {
if("IPAddress".equals(localName)){
newState = State.IP_ADDRESS;
}else if("HostName".equals(localName)){
newState = State.HOST_NAME;
}else if("LocalHost".equals(localName)){
newState = State.LOCALHOST;
}
break;
}
case LOCAL_PORT:
case REMOTE_PORT: {
if("Port".equals(localName)){
newState = State.PORT;
}
break;
}
case X_FORWARDED_FOR: {
if("ProxyIndex".equals(localName)){
newState = State.PROXY_IDX;
}else if("IPAddress".equals(localName)){
newState = State.IP_ADDRESS;
}else if("HostName".equals(localName)){
newState = State.HOST_NAME;
}else if("LocalHost".equals(localName)){
newState = State.LOCALHOST;
}else if("Empty".equals(localName)){
newState = State.OTHER;
addressCheckers.add(IPAddressCheckerEmpty.INSTANCE);
}
break;
}
case REQUEST_ATTR: {
if("Name".equals(localName)){
newState = State.REQUEST_ATTR_NAME;
}else if("Value".equals(localName)){
newState = State.STRING_MATCH;
}
break;
}
case DEFAULT_ACTION:
case ACTION: {
if("ALLOW".equals(localName)){
newState = State.OTHER;
actions.add(AllowAction.INSTANCE);
}else if("REJECT".equals(localName)){
newState = State.OTHER;
actions.add(RejectAction.INSTANCE);
}else if("SetRequestAttribute".equals(localName)){
newState = State.SET_REQUEST_ATTR;
}
break;
}
case HOST_NAME: {
if("Name".equals(localName)){
newState = State.STRING_MATCH;
}else if("Resolution".equals(localName)){
newState = State.RESOLUTION;
}
break;
}
case LOCALHOST: {
if("Resolution".equals(localName)){
newState = State.RESOLUTION;
}
break;
}
case STRING_MATCH: {
if("ExactMatch".equals(localName)){
newState = State.EXACT_MATCH;
}else if("RegexMatch".equals(localName)){
newState = State.REGEX_MATCH;
}
break;
}
case RESOLUTION: {
if("Startup".equals(localName)){
newState = State.OTHER;
resolution = Resolution.Startup;
}else if("OnDemand".equals(localName)){
newState = State.OTHER;
resolution = Resolution.OnDemand;
}
break;
}
case SET_REQUEST_ATTR: {
if("Name".equals(localName)){
newState = State.REQUEST_ATTR_NAME;
}else if("Value".equals(localName)){
newState = State.REQUEST_ATTR_VALUE;
}
break;
}
default: {
break;
}
}
}
state = newState;
if(state == State.UNKNOWN){
LOGGER.warn("({}) Unrecognized element: {} {}",
filterName, localName, getLocatorFormatter());
}
charBuffer.setLength(0);
}
@Override
public void characters(final char[] ch, final int start, final int length) throws SAXException{
charBuffer.append(ch, start, length);
}
protected <T> List<T> copyClear(final List<T> list){
final ArrayList<T> result = new ArrayList<T>(list);
// In JDK 1.5, the array will have been created with a capacity of 110% of the specified collection.
// In JDK 1.6 and 1.7, trimToSize() is technically unnecessary.
result.trimToSize();
list.clear();
return result;
}
@Override
public void endElement(final String uri, final String localName, final String qName) throws SAXException{
try{
switch(state){
case ADDRESS_GROUP: {
addressGroups.add(new AddressGroup(addressGroupId, copyClear(conditions), copyClear(actions)));
break;
}
case DEFAULT_ACTION: {
defaultActions.addAll(copyClear(groupActions));
break;
}
case LOCAL_ADDR: {
conditions.add(new LocalAddrCondition(copyClear(addressCheckers)));
break;
}
case LOCAL_PORT: {
if(portCheckers.size() > 1){
final List<ICondition> anyList = new ArrayList<ICondition>(portCheckers.size());
for(final IPortChecker checker : portCheckers){
anyList.add(new LocalPortCondition(checker));
}
conditions.add(new AnyCondition(anyList));
}else if(portCheckers.size() == 1){
conditions.add(new LocalPortCondition(portCheckers.get(0)));
}
portCheckers.clear();
break;
}
case REMOTE_ADDR: {
conditions.add(new RemoteAddrCondition(copyClear(addressCheckers)));
break;
}
case REMOTE_PORT: {
if(portCheckers.size() > 1){
final List<ICondition> anyList = new ArrayList<ICondition>(portCheckers.size());
for(final IPortChecker checker : portCheckers){
anyList.add(new RemotePortCondition(checker));
}
conditions.add(new AnyCondition(anyList));
}else if(portCheckers.size() == 1){
conditions.add(new RemotePortCondition(portCheckers.get(0)));
}
portCheckers.clear();
break;
}
case X_FORWARDED_FOR: {
conditions.add(new XForwardedForCondition(copyClear(addressCheckers), proxyIndex));
proxyIndex = 0;
break;
}
case IS_SECURE: {
conditions.add(new IsSecureCondition("true".equals(charBuffer.toString())));
break;
}
case SCHEME: {
conditions.add(new SchemeCondition(charBuffer.toString()));
break;
}
case USER_IN_ROLE: {
conditions.add(new UserInRoleCondition(charBuffer.toString()));
break;
}
case REQUEST_ATTR: {
conditions.add(new RequestAttributeCondition(requestAttrName, stringMatcher));
break;
}
case IP_ADDRESS: {
addressCheckers.add(IPAddressCheckerFactory.createChecker(charBuffer.toString()));
break;
}
case HOST_NAME: {
switch(resolution){
case Startup: {
if(!(stringMatcher instanceof StringMatcherExact)){
throw new Exception("HostName checker with Startup resolution must use ExactMatch.");
}
final String hostName = ((StringMatcherExact)stringMatcher).getValue();
addressCheckers.add(new HostNameStartupAddressChecker(hostName));
break;
}
case OnDemand: {
addressCheckers.add(new HostNameOnDemandAddressChecker(stringMatcher));
break;
}
default: {
throw new Exception("Unrecognized resolution type: " + resolution);
}
}
break;
}
case LOCALHOST: {
switch(resolution){
case Startup: {
addressCheckers.add(new LocalHostStartupAddressChecker());
break;
}
case OnDemand: {
addressCheckers.add(new LocalHostOnDemandAddressChecker());
break;
}
default: {
throw new Exception("Unrecognized resolution type: " + resolution);
}
}
break;
}
case PORT: {
portCheckers.add(IPPortCheckerFactory.createChecker(charBuffer.toString()));
break;
}
case PROXY_IDX: {
proxyIndex = Integer.parseInt(charBuffer.toString());
break;
}
case EXACT_MATCH: {
stringMatcher = new StringMatcherExact(charBuffer.toString());
break;
}
case REGEX_MATCH: {
stringMatcher = new StringMatcherRegex(charBuffer.toString());
break;
}
case SET_REQUEST_ATTR: {
actions.add(new RequestAttributeAction(requestAttrName, requestAttrValue));
break;
}
case REQUEST_ATTR_NAME: {
requestAttrName = charBuffer.toString();
break;
}
case REQUEST_ATTR_VALUE: {
requestAttrValue = charBuffer.toString();
break;
}
default: {
break;
}
}
}catch(final Exception ex){
throw new SAXParseException(ex.getMessage(), locator, ex);
}
state = stateStack.remove(stateStack.size() - 1);
charBuffer.setLength(0);
}
/**
* <p>
* Lazily builds the locator String, in case logging isn't enabled.
* </p>
*/
protected Object getLocatorFormatter(){
return new Object(){
final Locator locator = XmlConfigurator.this.locator;
@Override
public String toString(){
return "[line: " + locator.getLineNumber() + ", column: " + locator.getColumnNumber() + "]";
}
};
}
/**
* <p>
* Lazily builds the locator String, in case logging isn't enabled.
* ({@link SAXParseException}) should just implement {@link Locator}...)
* </p>
*/
protected Object getLocatorFormatter(final SAXParseException spe){
return new Object(){
@Override
public String toString(){
return "[line: " + spe.getLineNumber() + ", column: " + spe.getColumnNumber() + "]";
}
};
}
}