Sample code for BAEL-2539 (#7238)

* UPDATE Added junit5 & assertJ configuration in order to run tests

* FEAT Added JaxpTransformer basic implementation

* UPDATE Added JAXP polish version

* UPDATE Polish jaxp implementation

* FEAT Added complete JAXP Sample

* FIX typo

* Added additional samples and JMH benchmark

* FIX indent in Joox sample
This commit is contained in:
Juan Moreno 2019-07-23 00:16:48 -03:00 committed by KevinGilmore
parent 79a50387d1
commit 4bf6b77067
12 changed files with 469 additions and 12 deletions

View File

@ -1,4 +1,5 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>xml</artifactId>
@ -14,7 +15,7 @@
<dependencies>
<!-- xml libraries -->
<dependency>
<groupId>dom4j</groupId>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>${dom4j.version}</version>
</dependency>
@ -23,18 +24,31 @@
<artifactId>jaxen</artifactId>
<version>${jaxen.version}</version>
</dependency>
<dependency>
<groupId>org.jooq</groupId>
<artifactId>joox-java-6</artifactId>
<version>${joox.version}</version>
</dependency>
<dependency>
<groupId>org.jdom</groupId>
<artifactId>jdom2</artifactId>
<version>${jdom2.version}</version>
</dependency>
<dependency>
<groupId>javax.xml</groupId>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>${jaxb-api.version}</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>${jaxb-impl.version}</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>${jaxb-core.version}</version>
</dependency>
<dependency>
<groupId>javax.xml</groupId>
<artifactId>jaxp-api</artifactId>
@ -45,7 +59,17 @@
<artifactId>stax-api</artifactId>
<version>${stax-api.version}</version>
</dependency>
<!-- JMH Libraries -->
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>${jmh.version}</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>${jmh.version}</version>
</dependency>
<!-- utils -->
<dependency>
<groupId>commons-io</groupId>
@ -76,6 +100,31 @@
<artifactId>commons-lang</artifactId>
<version>${commons-lang.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj-core.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-assertj</artifactId>
<version>${xmlunit-assertj.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
@ -86,6 +135,16 @@
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
</plugin>
</plugins>
</build>
<profiles>
@ -213,7 +272,8 @@
</configuration>
</plugin>
<plugin>
<!-- NOTE: We don't need a groupId specification because the group is org.apache.maven.plugins ...which is assumed by default. -->
<!-- NOTE: We don't need a groupId specification
because the group is org.apache.maven.plugins ...which is assumed by default. -->
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archiveBaseDirectory>${project.basedir}</archiveBaseDirectory>
@ -232,8 +292,10 @@
</configuration>
<executions>
<execution>
<id>make-assembly</id><!-- this is used for inheritance merges -->
<phase>package</phase><!-- append to the packaging phase. -->
<id>make-assembly</id><!-- this is used for
inheritance merges -->
<phase>package</phase><!-- append to the
packaging phase. -->
<goals>
<goal>attached</goal><!-- goals == mojos -->
</goals>
@ -246,17 +308,31 @@
</profiles>
<properties>
<dom4j.version>1.6.1</dom4j.version>
<jaxen.version>1.1.6</jaxen.version>
<dom4j.version>2.1.1</dom4j.version>
<jaxen.version>1.2.0</jaxen.version>
<jdom2.version>2.0.6</jdom2.version>
<joox.version>1.6.2</joox.version>
<commons-io.version>2.5</commons-io.version>
<commons-collections4.version>4.1</commons-collections4.version>
<jibx-version>1.2.4.5</jibx-version>
<jaxb-api.version>2.1</jaxb-api.version>
<jaxb-api.version>2.3.1</jaxb-api.version>
<jaxp-api.version>1.4.2</jaxp-api.version>
<jaxb-core.version>2.3.0.1</jaxb-core.version>
<jaxb-impl.version>2.3.2</jaxb-impl.version>
<stax-api.version>1.0-2</stax-api.version>
<assertj-core.version>3.12.2</assertj-core.version>
<xmlunit-assertj.version>2.6.3</xmlunit-assertj.version>
<junit-jupiter.version>5.5.0</junit-jupiter.version>
<jmh.version>1.21</jmh.version>
<!-- util -->
<commons-lang3.version>3.5</commons-lang3.version>
<commons-lang.version>2.4</commons-lang.version>
<java-version>1.8</java-version>
<!-- maven plugins -->
<maven-jibx-plugin.version>1.3.1</maven-jibx-plugin.version>
<maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>
<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
</properties>
</project>

View File

@ -0,0 +1,45 @@
package com.baeldung.xml.attribute;
import org.dom4j.*;
import org.dom4j.io.DocumentSource;
import org.dom4j.io.SAXReader;
import javax.xml.XMLConstants;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import java.io.StringWriter;
import java.io.Writer;
import java.util.List;
public class Dom4jTransformer {
private final Document input;
public Dom4jTransformer(String resourcePath) throws DocumentException {
// 1- Build the doc from the XML file
SAXReader xmlReader = new SAXReader();
this.input = xmlReader.read(resourcePath);
}
public String modifyAttribute(String attribute, String oldValue, String newValue) throws TransformerException {
// 2- Locate the node(s) with xpath, we can use index and iterator too.
String expr = String.format("//*[contains(@%s, '%s')]", attribute, oldValue);
XPath xpath = DocumentHelper.createXPath(expr);
List<Node> nodes = xpath.selectNodes(input);
// 3- Make the change on the selected nodes
for (int i = 0; i < nodes.size(); i++) {
Element element = (Element) nodes.get(i);
element.addAttribute(attribute, newValue);
}
// 4- Save the result to a new XML doc
TransformerFactory factory = TransformerFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
Transformer xformer = factory.newTransformer();
xformer.setOutputProperty(OutputKeys.INDENT, "yes");
Writer output = new StringWriter();
xformer.transform(new DocumentSource(input), new StreamResult(output));
return output.toString();
}
}

View File

@ -0,0 +1,63 @@
package com.baeldung.xml.attribute;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
public class JaxpTransformer {
private Document input;
public JaxpTransformer(String resourcePath) throws SAXException, IOException, ParserConfigurationException {
// 1- Build the doc from the XML file
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
input = factory.newDocumentBuilder()
.parse(resourcePath);
}
public String modifyAttribute(String attribute, String oldValue, String newValue) throws XPathExpressionException, TransformerFactoryConfigurationError, TransformerException {
// 2- Locate the node(s) with xpath
XPath xpath = XPathFactory.newInstance()
.newXPath();
NodeList nodes = (NodeList) xpath.evaluate(String.format("//*[contains(@%s, '%s')]", attribute, oldValue), input, XPathConstants.NODESET);
// 3- Make the change on the selected nodes
for (int i = 0; i < nodes.getLength(); i++) {
Element value = (Element) nodes.item(i);
value.setAttribute(attribute, newValue);
}
//Stream api syntax
// IntStream
// .range(0, nodes.getLength())
// .mapToObj(i -> (Element) nodes.item(i))
// .forEach(value -> value.setAttribute(attribute, newValue));
// 4- Save the result to a new XML doc
TransformerFactory factory = TransformerFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
Transformer xformer = factory.newTransformer();
xformer.setOutputProperty(OutputKeys.INDENT, "yes");
Writer output = new StringWriter();
xformer.transform(new DOMSource(input), new StreamResult(output));
return output.toString();
}
}

View File

@ -0,0 +1,38 @@
package com.baeldung.xml.attribute;
import org.joox.JOOX;
import org.joox.Match;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.transform.TransformerFactoryConfigurationError;
import java.io.IOException;
import static org.joox.JOOX.$;
public class JooxTransformer {
private final Document input;
public JooxTransformer(String resourcePath) throws SAXException, IOException {
// 1- Build the doc from the XML file
DocumentBuilder builder = JOOX.builder();
input = builder.parse(resourcePath);
}
public String modifyAttribute(String attribute, String oldValue, String newValue) throws TransformerFactoryConfigurationError {
// 2- Select the document
Match $ = $(input);
// 3 - Find node to modify
String expr = String.format("//*[contains(@%s, '%s')]", attribute, oldValue);
$
// .find("to") or with xpath
.xpath(expr)
.get()
.stream()
.forEach(e -> e.setAttribute(attribute, newValue));
// 4- Return result as String
return $.toString();
}
}

View File

@ -0,0 +1,72 @@
package com.baeldung.xml.attribute.jmh;
import org.dom4j.DocumentException;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.xml.sax.SAXException;
import com.baeldung.xml.attribute.Dom4jTransformer;
import com.baeldung.xml.attribute.JaxpTransformer;
import com.baeldung.xml.attribute.JooxTransformer;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.xpath.XPathExpressionException;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
public class AttributeBenchMark {
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(AttributeBenchMark.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
}
@Benchmark
public String dom4jBenchmark() throws DocumentException, TransformerException {
String path = getClass()
.getResource("/xml/attribute.xml")
.toString();
Dom4jTransformer transformer = new Dom4jTransformer(path);
String attribute = "customer";
String oldValue = "true";
String newValue = "false";
return transformer.modifyAttribute(attribute, oldValue, newValue);
}
@Benchmark
public String jooxBenchmark() throws IOException, SAXException {
String path = getClass()
.getResource("/xml/attribute.xml")
.toString();
JooxTransformer transformer = new JooxTransformer(path);
String attribute = "customer";
String oldValue = "true";
String newValue = "false";
return transformer.modifyAttribute(attribute, oldValue, newValue);
}
@Benchmark
public String jaxpBenchmark() throws TransformerException, ParserConfigurationException, SAXException, IOException, XPathExpressionException {
String path = getClass()
.getResource("/xml/attribute.xml")
.toString();
JaxpTransformer transformer = new JaxpTransformer(path);
String attribute = "customer";
String oldValue = "true";
String newValue = "false";
return transformer.modifyAttribute(attribute, oldValue, newValue);
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<notification id="5">
<to customer="true">john@email.com</to>
<from>mary@email.com</from>
</notification>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<notification id="5">
<to customer="false">john@email.com</to>
<from>mary@email.com</from>
</notification>

View File

@ -0,0 +1,55 @@
package com.baeldung.xml.attribute;
import org.dom4j.DocumentException;
import org.junit.jupiter.api.Test;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactoryConfigurationError;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Paths;
import static org.xmlunit.assertj.XmlAssert.assertThat;
/**
* Unit test for {@link Dom4jTransformer}.
*/
public class Dom4jProcessorUnitTest {
@Test
public void givenXmlWithAttributes_whenModifyAttribute_thenGetXmlUpdated() throws TransformerFactoryConfigurationError, TransformerException, DocumentException {
String path = getClass()
.getResource("/xml/attribute.xml")
.toString();
Dom4jTransformer transformer = new Dom4jTransformer(path);
String attribute = "customer";
String oldValue = "true";
String newValue = "false";
String result = transformer.modifyAttribute(attribute, oldValue, newValue);
assertThat(result).hasXPath("//*[contains(@customer, 'false')]");
}
@Test
public void givenTwoXml_whenModifyAttribute_thenGetSimilarXml() throws IOException, TransformerFactoryConfigurationError, TransformerException, URISyntaxException, DocumentException {
String path = getClass()
.getResource("/xml/attribute.xml")
.toString();
Dom4jTransformer transformer = new Dom4jTransformer(path);
String attribute = "customer";
String oldValue = "true";
String newValue = "false";
String expectedXml = new String(Files.readAllBytes((Paths.get(getClass()
.getResource("/xml/attribute_expected.xml")
.toURI()))));
String result = transformer.modifyAttribute(attribute, oldValue, newValue);
assertThat(result)
.and(expectedXml)
.areSimilar();
}
}

View File

@ -0,0 +1,34 @@
package com.baeldung.xml.attribute;
import static org.xmlunit.assertj.XmlAssert.assertThat;
import java.io.IOException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.xpath.XPathExpressionException;
import org.junit.jupiter.api.Test;
import org.xml.sax.SAXException;
/**
* Unit test for {@link JaxpTransformer}.
*/
public class JaxpProcessorUnitTest {
@Test
public void givenXmlWithAttributes_whenModifyAttribute_thenGetXmlUpdated() throws IOException, SAXException, ParserConfigurationException, XPathExpressionException, TransformerFactoryConfigurationError, TransformerException {
String path = getClass().getResource("/xml/attribute.xml")
.toString();
JaxpTransformer transformer = new JaxpTransformer(path);
String attribute = "customer";
String oldValue = "true";
String newValue = "false";
String result = transformer.modifyAttribute(attribute, oldValue, newValue);
assertThat(result).hasXPath("//*[contains(@customer, 'false')]");
}
}

View File

@ -0,0 +1,54 @@
package com.baeldung.xml.attribute;
import org.junit.jupiter.api.Test;
import org.xml.sax.SAXException;
import javax.xml.transform.TransformerFactoryConfigurationError;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Paths;
import static org.xmlunit.assertj.XmlAssert.assertThat;
/**
* Unit test for {@link JooxTransformer}.
*/
public class JooxProcessorUnitTest {
@Test
public void givenXmlWithAttributes_whenModifyAttribute_thenGetXmlUpdated() throws IOException, SAXException, TransformerFactoryConfigurationError {
String path = getClass()
.getResource("/xml/attribute.xml")
.toString();
JooxTransformer transformer = new JooxTransformer(path);
String attribute = "customer";
String oldValue = "true";
String newValue = "false";
String result = transformer.modifyAttribute(attribute, oldValue, newValue);
assertThat(result).hasXPath("//*[contains(@customer, 'false')]");
}
@Test
public void givenTwoXml_whenModifyAttribute_thenGetSimilarXml() throws IOException, TransformerFactoryConfigurationError, URISyntaxException, SAXException {
String path = getClass()
.getResource("/xml/attribute.xml")
.toString();
JooxTransformer transformer = new JooxTransformer(path);
String attribute = "customer";
String oldValue = "true";
String newValue = "false";
String expectedXml = new String(Files.readAllBytes((Paths.get(getClass()
.getResource("/xml/attribute_expected.xml")
.toURI()))));
String result = transformer.modifyAttribute(attribute, oldValue, newValue);
assertThat(result)
.and(expectedXml)
.areSimilar();
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<notification id="5">
<to customer="true">john@email.com</to>
<from>mary@email.com</from>
</notification>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<notification id="5">
<to customer="false">john@email.com</to>
<from>mary@email.com</from>
</notification>