DbUnitFlatXmlReader.java

1
package com.reallifedeveloper.tools.test.database.dbunit;
2
3
import java.io.FileNotFoundException;
4
import java.io.IOException;
5
import java.io.InputStream;
6
import java.io.Serializable;
7
import java.util.ArrayList;
8
import java.util.List;
9
10
import javax.xml.XMLConstants;
11
import javax.xml.parsers.DocumentBuilder;
12
import javax.xml.parsers.DocumentBuilderFactory;
13
import javax.xml.parsers.ParserConfigurationException;
14
15
import org.slf4j.Logger;
16
import org.slf4j.LoggerFactory;
17
import org.springframework.data.repository.CrudRepository;
18
import org.w3c.dom.Document;
19
import org.w3c.dom.Element;
20
import org.w3c.dom.NamedNodeMap;
21
import org.w3c.dom.Node;
22
import org.w3c.dom.NodeList;
23
import org.xml.sax.SAXException;
24
25
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
26
27
import com.reallifedeveloper.tools.test.database.CrudRepositoryWriter;
28
import com.reallifedeveloper.tools.test.database.CrudRepositoryWriter.DbTableField;
29
import com.reallifedeveloper.tools.test.database.CrudRepositoryWriter.DbTableRow;
30
31
/**
32
 * A class to read a DBUnit flat XML dataset file and populate a Spring Data {@code CrudRepository} using the information in the file.
33
 * <p>
34
 * This is useful for testing in-memory repositories using the same test cases as for real repository implementations, and also for
35
 * populating in-memory repositories for testing services, without having to use a real database.
36
 *
37
 * @author RealLifeDeveloper
38
 */
39
@SuppressFBWarnings(value = "XXE_DOCUMENT", justification = "XML parser hardened as much as possible, see constructor")
40
public final class DbUnitFlatXmlReader {
41
42
    private static final Logger LOG = LoggerFactory.getLogger(DbUnitFlatXmlReader.class);
43
44
    private final DocumentBuilder documentBuilder;
45
    private final CrudRepositoryWriter crudRepositoryWriter = new CrudRepositoryWriter();
46
47
    /**
48
     * Creates a new {@code DbUnitFlatXmlReader}.
49
     */
50
    public DbUnitFlatXmlReader() {
51
        try {
52
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
53 1 1. <init> : removed call to javax/xml/parsers/DocumentBuilderFactory::setValidating → SURVIVED
            dbf.setValidating(false);
54 1 1. <init> : removed call to javax/xml/parsers/DocumentBuilderFactory::setNamespaceAware → SURVIVED
            dbf.setNamespaceAware(true);
55 1 1. <init> : removed call to javax/xml/parsers/DocumentBuilderFactory::setFeature → SURVIVED
            dbf.setFeature("http://xml.org/sax/features/namespaces", false);
56 1 1. <init> : removed call to javax/xml/parsers/DocumentBuilderFactory::setFeature → SURVIVED
            dbf.setFeature("http://xml.org/sax/features/validation", false);
57 1 1. <init> : removed call to javax/xml/parsers/DocumentBuilderFactory::setFeature → SURVIVED
            dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);
58 1 1. <init> : removed call to javax/xml/parsers/DocumentBuilderFactory::setFeature → KILLED
            dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
59
            // Configuration to make the parser safe from XXE attacks while still allowing
60
            // DTDs.
61
            // See
62
            // https://community.veracode.com/s/article/Java-Remediation-Guidance-for-XXE
63 1 1. <init> : removed call to javax/xml/parsers/DocumentBuilderFactory::setFeature → SURVIVED
            dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
64 1 1. <init> : removed call to javax/xml/parsers/DocumentBuilderFactory::setFeature → SURVIVED
            dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
65 1 1. <init> : removed call to javax/xml/parsers/DocumentBuilderFactory::setXIncludeAware → SURVIVED
            dbf.setXIncludeAware(false);
66 1 1. <init> : removed call to javax/xml/parsers/DocumentBuilderFactory::setExpandEntityReferences → SURVIVED
            dbf.setExpandEntityReferences(false);
67 1 1. <init> : removed call to javax/xml/parsers/DocumentBuilderFactory::setFeature → SURVIVED
            dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
68
            documentBuilder = dbf.newDocumentBuilder();
69
        } catch (ParserConfigurationException e) {
70
            throw new IllegalStateException("Unexpected problem creating XML parser", e);
71
        }
72
    }
73
74
    /**
75
     * Reads a DBUnit flat XML file from the named resource, populating the given repository with entities of the given type.
76
     *
77
     * @param resourceName         the classpath resource containing a DBUnit flat XML document
78
     * @param repository           the repository to populate with the entities from the XML document
79
     * @param repositoryEntityType the class object representing {@code <T>}, i.e., the class of the entities in the repository
80
     * @param entityType           the class object representing {@code <E>}, i.e., the class of the eneity being created
81
     *
82
     * @param <T>                  the type of entities in the repository
83
     * @param <E>                  the type of entity being created
84
     * @param <ID>                 the type of the primary key of the entities in the repository
85
     *
86
     * @throws IOException  if reading the file failed
87
     * @throws SAXException if parsing the file failed
88
     */
89
90
    public <T, E, ID extends Serializable> void read(String resourceName, CrudRepository<T, ID> repository, Class<T> repositoryEntityType,
91
            Class<E> entityType) throws IOException, SAXException {
92
        try (InputStream in = DbUnitFlatXmlReader.class.getResourceAsStream(resourceName)) {
93 1 1. read : negated conditional → KILLED
            if (in == null) {
94
                throw new FileNotFoundException(resourceName);
95
            }
96
            Document doc = documentBuilder.parse(in);
97
            Element dataset = doc.getDocumentElement();
98
            NodeList tableRows = dataset.getChildNodes();
99
100
            LOG.info("Reading from {}", resourceName.replaceAll("[\r\n]", ""));
101 2 1. read : negated conditional → KILLED
2. read : changed conditional boundary → KILLED
            for (int i = 0; i < tableRows.getLength(); i++) {
102
                Node tableRowNode = tableRows.item(i);
103 1 1. read : negated conditional → KILLED
                if (tableRowNode.getNodeType() == Node.ELEMENT_NODE) {
104
                    @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
105
                    DbTableRow tableRow = new NodeTableRow(tableRowNode);
106
                    String tableName = tableRowNode.getNodeName();
107 1 1. read : negated conditional → KILLED
                    if (crudRepositoryWriter.writeEntity(tableRow, repositoryEntityType, entityType, repository, tableName)) {
108
                        continue;
109
                    }
110 1 1. read : removed call to com/reallifedeveloper/tools/test/database/CrudRepositoryWriter::addEntitiesFromJoinTable → KILLED
                    crudRepositoryWriter.addEntitiesFromJoinTable(tableRow, tableName);
111
                }
112
            }
113 1 1. read : removed call to com/reallifedeveloper/tools/test/database/CrudRepositoryWriter::fillReferencesBetweenEntities → KILLED
            crudRepositoryWriter.fillReferencesBetweenEntities();
114
        } catch (ReflectiveOperationException | SecurityException e) {
115
            throw new IllegalStateException("Unexpected problem reading XML file from '" + resourceName + "'", e);
116
        }
117
    }
118
119
    /**
120
     * An implementation of the {@link DbTableRow} interface that gets its data from an * XML {@link Node} representing a single row in a
121
     * DBUnit XML dataset.
122
     * <p>
123
     * For example:
124
     *
125
     * <pre>
126
     * {@code
127
     * <dataset>
128
     *     ...
129
     *     <test_entity id="42" name="foo" />
130
     *     ...
131
     * </dataset>
132
     * }
133
     * </pre>
134
     */
135
    private static class NodeTableRow implements DbTableRow {
136
137
        private final Node tableRowNode;
138
139
        /* package-private */ NodeTableRow(Node tableRowNode) {
140
            this.tableRowNode = tableRowNode;
141
        }
142
143
        @Override
144
        public List<DbTableField> columns() {
145
            List<DbTableField> columns = new ArrayList<>();
146
            NamedNodeMap attributes = tableRowNode.getAttributes();
147 2 1. columns : negated conditional → KILLED
2. columns : changed conditional boundary → KILLED
            for (int j = 0; j < attributes.getLength(); j++) {
148
                Node attribute = attributes.item(j);
149
                columns.add(new DbTableField(attribute.getNodeName(), attribute.getNodeValue()));
150
            }
151 1 1. columns : replaced return value with Collections.emptyList for com/reallifedeveloper/tools/test/database/dbunit/DbUnitFlatXmlReader$NodeTableRow::columns → KILLED
            return columns;
152
        }
153
    }
154
}

Mutations

53

1.1
Location : <init>
Killed by : none
removed call to javax/xml/parsers/DocumentBuilderFactory::setValidating → SURVIVED
Covering tests

54

1.1
Location : <init>
Killed by : none
removed call to javax/xml/parsers/DocumentBuilderFactory::setNamespaceAware → SURVIVED
Covering tests

55

1.1
Location : <init>
Killed by : none
removed call to javax/xml/parsers/DocumentBuilderFactory::setFeature → SURVIVED
Covering tests

56

1.1
Location : <init>
Killed by : none
removed call to javax/xml/parsers/DocumentBuilderFactory::setFeature → SURVIVED
Covering tests

57

1.1
Location : <init>
Killed by : none
removed call to javax/xml/parsers/DocumentBuilderFactory::setFeature → SURVIVED
Covering tests

58

1.1
Location : <init>
Killed by : com.reallifedeveloper.tools.test.database.dbunit.DbUnitFlatXmlReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.database.dbunit.DbUnitFlatXmlReaderTest]/[method:readFileWithIncorrectAttribute()]
removed call to javax/xml/parsers/DocumentBuilderFactory::setFeature → KILLED

63

1.1
Location : <init>
Killed by : none
removed call to javax/xml/parsers/DocumentBuilderFactory::setFeature → SURVIVED
Covering tests

64

1.1
Location : <init>
Killed by : none
removed call to javax/xml/parsers/DocumentBuilderFactory::setFeature → SURVIVED
Covering tests

65

1.1
Location : <init>
Killed by : none
removed call to javax/xml/parsers/DocumentBuilderFactory::setXIncludeAware → SURVIVED
Covering tests

66

1.1
Location : <init>
Killed by : none
removed call to javax/xml/parsers/DocumentBuilderFactory::setExpandEntityReferences → SURVIVED
Covering tests

67

1.1
Location : <init>
Killed by : none
removed call to javax/xml/parsers/DocumentBuilderFactory::setFeature → SURVIVED
Covering tests

93

1.1
Location : read
Killed by : com.reallifedeveloper.tools.test.database.dbunit.DbUnitFlatXmlReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.database.dbunit.DbUnitFlatXmlReaderTest]/[method:readNonExistingFile()]
negated conditional → KILLED

101

1.1
Location : read
Killed by : com.reallifedeveloper.tools.test.database.dbunit.DbUnitFlatXmlReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.database.dbunit.DbUnitFlatXmlReaderTest]/[method:readFileWithIncorrectAttribute()]
negated conditional → KILLED

2.2
Location : read
Killed by : com.reallifedeveloper.tools.test.database.dbunit.DbUnitFlatXmlReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.database.dbunit.DbUnitFlatXmlReaderTest]/[method:readFileForSimpleEntity()]
changed conditional boundary → KILLED

103

1.1
Location : read
Killed by : com.reallifedeveloper.tools.test.database.dbunit.DbUnitFlatXmlReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.database.dbunit.DbUnitFlatXmlReaderTest]/[method:readFileWithIncorrectAttribute()]
negated conditional → KILLED

107

1.1
Location : read
Killed by : com.reallifedeveloper.tools.test.database.dbunit.DbUnitFlatXmlReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.database.dbunit.DbUnitFlatXmlReaderTest]/[method:readFileForEntityWithAssociations()]
negated conditional → KILLED

110

1.1
Location : read
Killed by : com.reallifedeveloper.tools.test.database.dbunit.DbUnitFlatXmlReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.database.dbunit.DbUnitFlatXmlReaderTest]/[method:readFileForEntityWithAssociations()]
removed call to com/reallifedeveloper/tools/test/database/CrudRepositoryWriter::addEntitiesFromJoinTable → KILLED

113

1.1
Location : read
Killed by : com.reallifedeveloper.tools.test.database.dbunit.DbUnitFlatXmlReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.database.dbunit.DbUnitFlatXmlReaderTest]/[method:readFileForEntityWithAssociations()]
removed call to com/reallifedeveloper/tools/test/database/CrudRepositoryWriter::fillReferencesBetweenEntities → KILLED

147

1.1
Location : columns
Killed by : com.reallifedeveloper.tools.test.database.dbunit.DbUnitFlatXmlReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.database.dbunit.DbUnitFlatXmlReaderTest]/[method:readFileWithIncorrectAttribute()]
negated conditional → KILLED

2.2
Location : columns
Killed by : com.reallifedeveloper.tools.test.database.dbunit.DbUnitFlatXmlReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.database.dbunit.DbUnitFlatXmlReaderTest]/[method:readFileWithIncorrectAttribute()]
changed conditional boundary → KILLED

151

1.1
Location : columns
Killed by : com.reallifedeveloper.tools.test.database.dbunit.DbUnitFlatXmlReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.tools.test.database.dbunit.DbUnitFlatXmlReaderTest]/[method:readFileWithIncorrectAttribute()]
replaced return value with Collections.emptyList for com/reallifedeveloper/tools/test/database/dbunit/DbUnitFlatXmlReader$NodeTableRow::columns → KILLED

Active mutators

Tests examined


Report generated by PIT 1.23.0