package com.philemonworks.critter.rule;
import com.google.common.collect.Sets;
import com.philemonworks.critter.action.*;
import com.philemonworks.critter.condition.*;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XppDriver;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.io.*;
import java.util.Set;
public class RuleConverter {
private static final Logger LOG = LoggerFactory.getLogger(RuleConverter.class);
private static XStream getXStream() {
XStream xs = newXStream();
xs.alias("rule", Rule.class);
xs.useAttributeFor(Rule.class, "id");
xs.useAttributeFor(Rule.class, "enabled");
xs.useAttributeFor(Rule.class, "tracing");
xs.omitField(Rule.class, "invalid");
// conditions
xs.alias("host", Host.class);
xs.useAttributeFor(Host.class, "matches");
// is action too
xs.useAttributeFor(Host.class, "value");
xs.alias("method", Method.class);
xs.useAttributeFor(Method.class, "matches");
xs.alias("path", Path.class);
xs.useAttributeFor(Path.class, "matches");
xs.alias("requestheader", RequestHeader.class);
xs.useAttributeFor(RequestHeader.class, "name");
xs.useAttributeFor(RequestHeader.class, "matches");
xs.alias("equals", Equals.class);
xs.useAttributeFor(Equals.class, "parameter");
xs.useAttributeFor(Equals.class, "value");
xs.alias("port", Port.class);
xs.useAttributeFor(Port.class, "matches");
// is action too
xs.useAttributeFor(Port.class, "value");
xs.alias("basicauthentication", BasicAuthentication.class);
xs.useAttributeFor(BasicAuthentication.class, "username");
xs.useAttributeFor(BasicAuthentication.class, "password");
xs.alias("not", Not.class);
xs.registerConverter(new NotConverter());
xs.alias("xpath", XPath.class);
xs.useAttributeFor(XPath.class, "expression");
xs.useAttributeFor(XPath.class, "matches");
xs.alias("protobufpath", ProtobufPath.class);
xs.useAttributeFor(ProtobufPath.class, "messageName");
xs.useAttributeFor(ProtobufPath.class, "expression");
xs.useAttributeFor(ProtobufPath.class, "matches");
xs.alias("requestbody", RequestBody.class);
xs.useAttributeFor(RequestBody.class, "matches");
xs.alias("script", Script.class);
// actions
xs.alias("forward", Forward.class);
xs.alias("delay", Delay.class);
xs.useAttributeFor(Delay.class, "milliSeconds");
xs.aliasAttribute("ms", "milliSeconds");
xs.alias("status", StatusCode.class);
xs.useAttributeFor(StatusCode.class, "code");
xs.alias("scheme", Scheme.class);
xs.useAttributeFor(Scheme.class, "name");
xs.alias("respond", Respond.class);
xs.alias("responsebody", ResponseBody.class);
xs.useAttributeFor(ResponseBody.class, "base64");
xs.alias("responseheader", ResponseHeader.class);
xs.useAttributeFor(ResponseHeader.class, "add");
xs.useAttributeFor(ResponseHeader.class, "remove");
xs.useAttributeFor(ResponseHeader.class, "value");
xs.alias("close", Close.class);
xs.alias("trace", Trace.class);
xs.alias("record", Record.class);
xs.alias("digestauthentication", DigestAuthentication.class);
xs.useAttributeFor(DigestAuthentication.class, "username");
xs.useAttributeFor(DigestAuthentication.class, "password");
xs.useAttributeFor(DigestAuthentication.class, "realm");
return xs;
}
@SuppressWarnings("unchecked")
public static <T> T fromXml(InputStream is) {
StringWriter stringWriter = new StringWriter();
try {
IOUtils.copy(is, stringWriter);
} catch (IOException e) {
throw new RuntimeException(e);
}
return fromXml(stringWriter.toString(), true);
}
@SuppressWarnings("unchecked")
public static <T> T fromXml(final String xml, boolean doValidate) {
if (doValidate) {
String errorMessage = validateXML(xml);
if (errorMessage.length() > 0) {
throw new RuntimeException(errorMessage);
}
}
T object = (T) getXStream().fromXML(xml);
return object;
}
public static String validateXML(final String xml) {
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
ErrorCollector errorHandler = new ErrorCollector();
try {
Schema schema = schemaFactory.newSchema(RuleConverter.class.getResource("/critter.xsd"));
Validator validator = schema.newValidator();
validator.setErrorHandler(errorHandler);
validator.validate(new StreamSource(new StringReader(xml)));
} catch (SAXException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
if (errorHandler.exceptions.size() > 0) {
LOG.error("there are validation exceptions: {}", errorHandler.exceptions);
return StringUtils.join(errorHandler.exceptions, "<br>");
}
return ""; // valid
}
public static String toXml(Object o) {
return getXStream().toXML(o);
}
// http://oktryitnow.com/?p=11
private static XStream newXStream() {
return new XStream(new XppDriver() {
public HierarchicalStreamWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
boolean cdata = false;
@SuppressWarnings("rawtypes")
public void startNode(String name, Class clazz) {
super.startNode(name, clazz);
cdata = name.equals("body");
}
protected void writeText(QuickWriter writer, String text) {
if (cdata) {
writer.write("<![CDATA[");
writer.write(text);
writer.write("]]>");
} else {
writer.write(text);
}
}
};
}
});
}
private static class ErrorCollector implements ErrorHandler {
private Set<String> exceptions = Sets.newLinkedHashSet();
@Override
public void warning(SAXParseException exception) throws SAXException {
LOG.debug("SAX.warning {}", exception);
}
@Override
public void error(SAXParseException exception) throws SAXException {
LOG.debug("SAX.error {}", exception.toString());
LOG.debug("\t{}", exception.getColumnNumber());
LOG.debug("\t{}", exception.getLineNumber());
LOG.debug("\t{}", exception.getPublicId());
LOG.debug("\t{}", exception.getSystemId());
LOG.debug("\t{}", exception.getMessage());
exceptions.add(exception.getMessage());
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
LOG.debug("SAX.fatalError {}", exception);
throw new SAXException("invalid xml");
}
}
}