diff --git a/client/.classpath b/client/.classpath
index 8ea7380e16..7522075332 100644
--- a/client/.classpath
+++ b/client/.classpath
@@ -122,7 +122,6 @@
-
@@ -213,7 +212,6 @@
-
diff --git a/client/lib/xercesImpl-2.12.2.jar b/client/lib/xercesImpl-2.12.2.jar
deleted file mode 100644
index ccbae9f456..0000000000
Binary files a/client/lib/xercesImpl-2.12.2.jar and /dev/null differ
diff --git a/client/lib/xml-apis-1.4.01.jar b/client/lib/xml-apis-1.4.01.jar
deleted file mode 100644
index 46733464fc..0000000000
Binary files a/client/lib/xml-apis-1.4.01.jar and /dev/null differ
diff --git a/server/.classpath b/server/.classpath
index 9ff5df7f09..aaac1b455c 100644
--- a/server/.classpath
+++ b/server/.classpath
@@ -269,7 +269,5 @@
-
-
diff --git a/server/docs/thirdparty/THIRD-PARTY-README.txt b/server/docs/thirdparty/THIRD-PARTY-README.txt
index a1cc3d6442..ed5ff9b4ac 100644
--- a/server/docs/thirdparty/THIRD-PARTY-README.txt
+++ b/server/docs/thirdparty/THIRD-PARTY-README.txt
@@ -521,16 +521,6 @@ License: LGPL (dual-license with SPL)
Use version 2.x as Xilize has a dependency on it; used only to produce
documentation.
-XML-APIs (extracted from Apache Xerces-2)
-http://xerces.apache.org/xerces2-j/
-License: Apache v2
-We include the xml-apis.jar from the Xerces binary distribution in order to allow
-our code to compile on JDK 1.4, which does not include newer XML APIs, even though
-these API implementations will run on version 1.4 of the JRE. The JAR is unmodified
-from the Xerces release, but is renamed as xml-apis-xerces-2.9.1.jar to
-make the version clear.
-Included as lib/xml-apis-xerces-2.9.1.jar
-
=================== End of License Information ===================
@@ -883,74 +873,6 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
-The license below pertains to Apache Xerces2 Java (Build Tools) version 2.9.1,
-which is included with Mirth Connect.
-
-=================== Beginning of License ===================
-
-/*
- * The Apache Software License, Version 1.1
- *
- *
- * Copyright (c) 1999-2002 The Apache Software Foundation. All rights
- * reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * 2. 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.
- *
- * 3. The end-user documentation included with the redistribution,
- * if any, must include the following acknowledgment:
- * "This product includes software developed by the
- * Apache Software Foundation (http://www.apache.org/)."
- * Alternately, this acknowledgment may appear in the software itself,
- * if and wherever such third-party acknowledgments normally appear.
- *
- * 4. The names "Xerces" and "Apache Software Foundation" must
- * not be used to endorse or promote products derived from this
- * software without prior written permission. For written
- * permission, please contact apache@apache.org.
- *
- * 5. Products derived from this software may not be called "Apache",
- * nor may "Apache" appear in their name, without prior written
- * permission of the Apache Software Foundation.
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 APACHE SOFTWARE FOUNDATION OR
- * ITS 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.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation and was
- * originally based on software copyright (c) 1999, International
- * Business Machines, Inc., http://www.ibm.com. For more
- * information on the Apache Software Foundation, please see
- * .
- */
-
-=================== End of License =========================
-
-
---------------------------------------------------------------------------------
-
-
The license below pertains to Jsch version 0.2.13, which is included with Mirth
Connect.
diff --git a/server/lib/xercesImpl-2.12.2.jar b/server/lib/xercesImpl-2.12.2.jar
deleted file mode 100644
index ccbae9f456..0000000000
Binary files a/server/lib/xercesImpl-2.12.2.jar and /dev/null differ
diff --git a/server/lib/xml-apis-1.4.01.jar b/server/lib/xml-apis-1.4.01.jar
deleted file mode 100644
index 46733464fc..0000000000
Binary files a/server/lib/xml-apis-1.4.01.jar and /dev/null differ
diff --git a/server/src/com/mirth/connect/plugins/datatypes/delimited/DelimitedReader.java b/server/src/com/mirth/connect/plugins/datatypes/delimited/DelimitedReader.java
index 1cc0e4a45d..a3da25c9b9 100644
--- a/server/src/com/mirth/connect/plugins/datatypes/delimited/DelimitedReader.java
+++ b/server/src/com/mirth/connect/plugins/datatypes/delimited/DelimitedReader.java
@@ -1,11 +1,6 @@
-/*
- * Copyright (c) Mirth Corporation. All rights reserved.
- *
- * http://www.mirthcorp.com
- *
- * The software in this package is published under the terms of the MPL license a copy of which has
- * been included with this distribution in the LICENSE.txt file.
- */
+// SPDX-License-Identifier: MPL-2.0
+// SPDX-FileCopyrightText: Mirth Corporation
+// SPDX-FileCopyrightText: 2025 Tony Germano
package com.mirth.connect.plugins.datatypes.delimited;
@@ -16,14 +11,13 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
-import org.apache.xerces.parsers.SAXParser;
-import org.xml.sax.ContentHandler;
+import org.openintegrationengine.engine.plugins.datatypes.AbstractXMLReader;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import com.mirth.connect.util.StringUtil;
-public class DelimitedReader extends SAXParser {
+public class DelimitedReader extends AbstractXMLReader {
private Logger logger = LogManager.getLogger(this.getClass());
private DelimitedSerializationProperties serializationProperties;
@@ -49,8 +43,10 @@ public DelimitedReader(DelimitedSerializationProperties serializationProperties)
ungottenRawText = null;
}
+ @Override
public void parse(InputSource input) throws SAXException, IOException {
-
+ ensureHandlerSet();
+
// Parsing overview
//
// The incoming stream is a single message which is a collection of one
@@ -85,11 +81,10 @@ public void parse(InputSource input) throws SAXException, IOException {
// Start the document
String documentHead = "delimited";
- ContentHandler contentHandler = getContentHandler();
contentHandler.startDocument();
// Output
- contentHandler.startElement("", documentHead, "", null);
+ contentHandler.startElement("", documentHead, "", getEmptyAttributes());
// While the parser gets records from the message
ArrayList record;
@@ -98,9 +93,9 @@ public void parse(InputSource input) throws SAXException, IOException {
// Output
if (serializationProperties.isNumberedRows()) {
- contentHandler.startElement("", "row" + recordNo, "", null);
+ contentHandler.startElement("", "row" + recordNo, "", getEmptyAttributes());
} else {
- contentHandler.startElement("", "row", "", null);
+ contentHandler.startElement("", "row", "", getEmptyAttributes());
}
// For each column
@@ -115,10 +110,11 @@ public void parse(InputSource input) throws SAXException, IOException {
columnName = "column" + (i + 1);
}
// Output
- contentHandler.startElement("", columnName, "", null);
+ contentHandler.startElement("", columnName, "", getEmptyAttributes());
// Output column value
- contentHandler.characters(record.get(i).toCharArray(), 0, record.get(i).length());
+ String val = record.get(i);
+ contentHandler.characters(val.toCharArray(), 0, val.length());
// Output
contentHandler.endElement("", columnName, "");
diff --git a/server/src/com/mirth/connect/plugins/datatypes/edi/EDIReader.java b/server/src/com/mirth/connect/plugins/datatypes/edi/EDIReader.java
index 8260653865..db55cc8590 100644
--- a/server/src/com/mirth/connect/plugins/datatypes/edi/EDIReader.java
+++ b/server/src/com/mirth/connect/plugins/datatypes/edi/EDIReader.java
@@ -1,11 +1,6 @@
-/*
- * Copyright (c) Mirth Corporation. All rights reserved.
- *
- * http://www.mirthcorp.com
- *
- * The software in this package is published under the terms of the MPL license a copy of which has
- * been included with this distribution in the LICENSE.txt file.
- */
+// SPDX-License-Identifier: MPL-2.0
+// SPDX-FileCopyrightText: Mirth Corporation
+// SPDX-FileCopyrightText: 2025 Tony Germano
package com.mirth.connect.plugins.datatypes.edi;
@@ -15,19 +10,17 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
-import org.apache.xerces.parsers.SAXParser;
+import org.openintegrationengine.engine.plugins.datatypes.AbstractXMLReader;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
-public class EDIReader extends SAXParser {
+public class EDIReader extends AbstractXMLReader {
private Logger logger = LogManager.getLogger(this.getClass());
private String segmentDelimiter;
-
private String elementDelimiter;
-
private String subelementDelimiter;
public EDIReader(String segmentDelimiter, String elementDelimiter, String subelementDelimiter) {
@@ -37,7 +30,10 @@ public EDIReader(String segmentDelimiter, String elementDelimiter, String subele
return;
}
+ @Override
public void parse(InputSource input) throws SAXException, IOException {
+ ensureHandlerSet();
+
// Read the data from the InputSource
BufferedReader in = new BufferedReader(input.getCharacterStream());
String nextLine = "";
@@ -78,13 +74,13 @@ public void parse(InputSource input) throws SAXException, IOException {
documentHead = "EDIMessage";
}
- AttributesImpl attributesImpl = new AttributesImpl();
+ AttributesImpl attributesImpl = getEmptyAttributes();
attributesImpl.addAttribute("", "segmentDelimiter", "", "", segmentDelimiter);
attributesImpl.addAttribute("", "elementDelimiter", "", "", elementDelimiter);
attributesImpl.addAttribute("", "subelementDelimiter", "", "", subelementDelimiter);
contentHandler.startElement("", documentHead, "", attributesImpl);
}
- contentHandler.startElement("", segmentID, "", null);
+ contentHandler.startElement("", segmentID, "", getEmptyAttributes());
int fieldID = 0;
String field = "00";
@@ -101,7 +97,7 @@ public void parse(InputSource input) throws SAXException, IOException {
// The naming is SEG.
if (element.equals(elementDelimiter)) {
if (lastsegElement) {
- contentHandler.startElement("", segmentID + "." + field, "", null);
+ contentHandler.startElement("", segmentID + "." + field, "", getEmptyAttributes());
contentHandler.endElement("", segmentID + "." + field, "");
}
fieldID++;
@@ -110,7 +106,7 @@ public void parse(InputSource input) throws SAXException, IOException {
lastsegElement = false;
if (element.indexOf(subelementDelimiter) > -1) {
- contentHandler.startElement("", segmentID + "." + field, "", null);
+ contentHandler.startElement("", segmentID + "." + field, "", getEmptyAttributes());
// check if we have sub-elements, if so add them
StringTokenizer subelementTokenizer = new StringTokenizer(element, subelementDelimiter, true);
subelementID = 1;
@@ -120,7 +116,7 @@ public void parse(InputSource input) throws SAXException, IOException {
if (subelement.equals(subelementDelimiter)) {
String subelementName = segmentID + "." + field + "." + subelementID;
if (lastsegSubelement) {
- contentHandler.startElement("", subelementName, "", null);
+ contentHandler.startElement("", subelementName, "", getEmptyAttributes());
contentHandler.characters("".toCharArray(), 0, 0);
contentHandler.endElement("", subelementName, "");
}
@@ -132,7 +128,7 @@ public void parse(InputSource input) throws SAXException, IOException {
lastsegSubelement = false;
// The naming is SEG..
- contentHandler.startElement("", subelementName, "", null);
+ contentHandler.startElement("", subelementName, "", getEmptyAttributes());
contentHandler.characters(subelement.toCharArray(), 0, subelement.length());
contentHandler.endElement("", subelementName, "");
@@ -140,19 +136,19 @@ public void parse(InputSource input) throws SAXException, IOException {
}
String subelementName = segmentID + "." + (field) + "." + subelementID;
if (lastsegSubelement) {
- contentHandler.startElement("", subelementName, "", null);
+ contentHandler.startElement("", subelementName, "", getEmptyAttributes());
contentHandler.characters("".toCharArray(), 0, 0);
contentHandler.endElement("", subelementName, "");
}
- contentHandler.endElement("", segmentID + "." + (field), null);
+ contentHandler.endElement("", segmentID + "." + (field), "");
} else {
- contentHandler.startElement("", segmentID + "." + field, "", null);
- contentHandler.startElement("", segmentID + "." + field + ".1", "", null);
+ contentHandler.startElement("", segmentID + "." + field, "", getEmptyAttributes());
+ contentHandler.startElement("", segmentID + "." + field + ".1", "", getEmptyAttributes());
// Set the text contents to the value
contentHandler.characters(element.toCharArray(), 0, element.length());
- contentHandler.endElement("", segmentID + "." + (field) + ".1", null);
- contentHandler.endElement("", segmentID + "." + (field), null);
+ contentHandler.endElement("", segmentID + "." + (field) + ".1", "");
+ contentHandler.endElement("", segmentID + "." + (field), "");
}
}
@@ -162,7 +158,7 @@ public void parse(InputSource input) throws SAXException, IOException {
// Set the field id here so we don't get dupe fields like
// SE.01 and SE.01 when we have SE**~
field = fieldID < 10 ? "0" + fieldID : "" + fieldID;
- contentHandler.startElement("", segmentID + "." + field, "", null);
+ contentHandler.startElement("", segmentID + "." + field, "", getEmptyAttributes());
contentHandler.endElement("", segmentID + "." + field, "");
}
contentHandler.endElement("", segmentID, "");
@@ -175,5 +171,4 @@ public void parse(InputSource input) throws SAXException, IOException {
contentHandler.endElement("", documentHead, "");
contentHandler.endDocument();
}
-
}
diff --git a/server/src/com/mirth/connect/plugins/datatypes/hl7v2/ER7Reader.java b/server/src/com/mirth/connect/plugins/datatypes/hl7v2/ER7Reader.java
index 0c1b4f7251..f6c68e6d35 100644
--- a/server/src/com/mirth/connect/plugins/datatypes/hl7v2/ER7Reader.java
+++ b/server/src/com/mirth/connect/plugins/datatypes/hl7v2/ER7Reader.java
@@ -1,11 +1,6 @@
-/*
- * Copyright (c) Mirth Corporation. All rights reserved.
- *
- * http://www.mirthcorp.com
- *
- * The software in this package is published under the terms of the MPL license a copy of which has
- * been included with this distribution in the LICENSE.txt file.
- */
+// SPDX-License-Identifier: MPL-2.0
+// SPDX-FileCopyrightText: Mirth Corporation
+// SPDX-FileCopyrightText: 2025 Tony Germano
package com.mirth.connect.plugins.datatypes.hl7v2;
@@ -16,13 +11,14 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
-import org.apache.xerces.parsers.SAXParser;
+import org.openintegrationengine.engine.plugins.datatypes.AbstractXMLReader;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
-public class ER7Reader extends SAXParser {
+public class ER7Reader extends AbstractXMLReader {
private Logger logger = LogManager.getLogger(this.getClass());
+
private boolean handleRepetitions = false;
private boolean handleSubcomponents = false;
private String segmentDelimiter;
@@ -54,7 +50,10 @@ private String getMessageFromSource(InputSource source) throws IOException {
return builder.toString().trim();
}
+ @Override
public void parse(InputSource source) throws SAXException, IOException {
+ ensureHandlerSet();
+
String message = getMessageFromSource(source);
ContentHandler contentHandler = getContentHandler();
contentHandler.startDocument();
@@ -131,10 +130,10 @@ private String handleSegments(String documentHead, ContentHandler contentHandler
if (segmentIndex == 0) {
documentHead = MESSAGE_ROOT_ID;
- contentHandler.startElement("", documentHead, "", null);
+ contentHandler.startElement("", documentHead, "", getEmptyAttributes());
}
- contentHandler.startElement("", segmentId, "", null);
+ contentHandler.startElement("", segmentId, "", getEmptyAttributes());
handleFieldOrRepetitions(contentHandler, fieldSeparator, componentSeparator, subcomponentSeparator, repetitionSeparator, escapeCharacter, segmentId, fieldTokenizer);
contentHandler.endElement("", segmentId, "");
} else {
@@ -160,7 +159,7 @@ private void handleFieldOrRepetitions(ContentHandler contentHandler, String fiel
// the naming is SEG.
if (field.equals(fieldSeparator)) {
if (atLastField) {
- contentHandler.startElement("", segmentId + "." + fieldId, "", null);
+ contentHandler.startElement("", segmentId + "." + fieldId, "", getEmptyAttributes());
contentHandler.endElement("", segmentId + "." + fieldId, "");
}
@@ -176,11 +175,11 @@ private void handleFieldOrRepetitions(ContentHandler contentHandler, String fiel
}
if (enteredHeader && (fieldId == 1)) {
- contentHandler.startElement("", segmentId + "." + fieldId, "", null);
+ contentHandler.startElement("", segmentId + "." + fieldId, "", getEmptyAttributes());
contentHandler.characters(fieldSeparator.toCharArray(), 0, 1);
- contentHandler.endElement("", segmentId + "." + (fieldId), null);
+ contentHandler.endElement("", segmentId + "." + (fieldId), "");
fieldId++;
- contentHandler.startElement("", segmentId + "." + fieldId, "", null);
+ contentHandler.startElement("", segmentId + "." + fieldId, "", getEmptyAttributes());
char[] specialCharacters;
if (!subcomponentSeparator.isEmpty()) {
@@ -196,7 +195,7 @@ private void handleFieldOrRepetitions(ContentHandler contentHandler, String fiel
}
contentHandler.characters(specialCharacters, 0, specialCharacters.length);
- contentHandler.endElement("", segmentId + "." + (fieldId), null);
+ contentHandler.endElement("", segmentId + "." + (fieldId), "");
} else if (enteredHeader && (fieldId == 2)) {
// do nothing
} else {
@@ -210,7 +209,7 @@ private void handleFieldOrRepetitions(ContentHandler contentHandler, String fiel
}
if (atLastField) {
- contentHandler.startElement("", segmentId + "." + fieldId, "", null);
+ contentHandler.startElement("", segmentId + "." + fieldId, "", getEmptyAttributes());
contentHandler.endElement("", segmentId + "." + fieldId, "");
}
}
@@ -225,7 +224,7 @@ private void handleFieldRepetitions(ContentHandler contentHandler, String compon
if (field.equals(repetitionSeparator)) {
// check for ~~
if (atLastRepetition) {
- contentHandler.startElement("", segmentId + "." + fieldId, "", null);
+ contentHandler.startElement("", segmentId + "." + fieldId, "", getEmptyAttributes());
contentHandler.characters(EMPTY_CHAR_ARRAY, 0, 0);
contentHandler.endElement("", segmentId + "." + fieldId, "");
}
@@ -239,7 +238,7 @@ private void handleFieldRepetitions(ContentHandler contentHandler, String compon
}
if (atLastRepetition) {
- contentHandler.startElement("", segmentId + "." + fieldId, "", null);
+ contentHandler.startElement("", segmentId + "." + fieldId, "", getEmptyAttributes());
contentHandler.characters(EMPTY_CHAR_ARRAY, 0, 0);
contentHandler.endElement("", segmentId + "." + fieldId, "");
}
@@ -247,17 +246,17 @@ private void handleFieldRepetitions(ContentHandler contentHandler, String compon
private void handleField(ContentHandler contentHandler, String componentSeparator, String subcomponentSeparator, String segmentId, int fieldId, String field) throws SAXException {
if ((field.indexOf(componentSeparator) > -1) || (handleSubcomponents && (field.indexOf(subcomponentSeparator) > -1))) {
- contentHandler.startElement("", segmentId + "." + fieldId, "", null);
+ contentHandler.startElement("", segmentId + "." + fieldId, "", getEmptyAttributes());
StringTokenizer componentTokenizer = new StringTokenizer(field, componentSeparator, true);
handleComponents(contentHandler, componentSeparator, subcomponentSeparator, segmentId, fieldId, 1, componentTokenizer);
- contentHandler.endElement("", segmentId + "." + fieldId, null);
+ contentHandler.endElement("", segmentId + "." + fieldId, "");
} else {
logger.trace("handling field: " + field);
- contentHandler.startElement("", segmentId + "." + fieldId, "", null);
- contentHandler.startElement("", segmentId + "." + fieldId + ".1", "", null);
+ contentHandler.startElement("", segmentId + "." + fieldId, "", getEmptyAttributes());
+ contentHandler.startElement("", segmentId + "." + fieldId + ".1", "", getEmptyAttributes());
contentHandler.characters(field.toCharArray(), 0, field.length());
- contentHandler.endElement("", segmentId + "." + fieldId + ".1", null);
- contentHandler.endElement("", segmentId + "." + fieldId, null);
+ contentHandler.endElement("", segmentId + "." + fieldId + ".1", "");
+ contentHandler.endElement("", segmentId + "." + fieldId, "");
}
}
@@ -269,7 +268,7 @@ private void handleComponents(ContentHandler contentHandler, String componentSep
if (component.equals(componentSeparator)) {
if (atLastComponent) {
- contentHandler.startElement("", segmentId + "." + fieldId + "." + componentId, "", null);
+ contentHandler.startElement("", segmentId + "." + fieldId + "." + componentId, "", getEmptyAttributes());
contentHandler.characters(EMPTY_CHAR_ARRAY, 0, 0);
contentHandler.endElement("", segmentId + "." + fieldId + "." + componentId, "");
}
@@ -283,7 +282,7 @@ private void handleComponents(ContentHandler contentHandler, String componentSep
}
if (atLastComponent) {
- contentHandler.startElement("", segmentId + "." + fieldId + "." + componentId, "", null);
+ contentHandler.startElement("", segmentId + "." + fieldId + "." + componentId, "", getEmptyAttributes());
contentHandler.characters(EMPTY_CHAR_ARRAY, 0, 0);
contentHandler.endElement("", segmentId + "." + fieldId + "." + componentId, "");
}
@@ -291,15 +290,15 @@ private void handleComponents(ContentHandler contentHandler, String componentSep
private void handleComponent(ContentHandler contentHandler, String subcomponentSeparator, String segmentId, int fieldId, int componentId, String component) throws SAXException {
if (handleSubcomponents && !subcomponentSeparator.isEmpty() && (component.indexOf(subcomponentSeparator) > -1)) {
- contentHandler.startElement("", segmentId + "." + fieldId + "." + componentId, "", null);
+ contentHandler.startElement("", segmentId + "." + fieldId + "." + componentId, "", getEmptyAttributes());
// check if we have subcomponents, if so add them
StringTokenizer subcomponentTokenizer = new StringTokenizer(component, subcomponentSeparator, true);
handleSubcomponents(contentHandler, subcomponentSeparator, segmentId, fieldId, componentId, 1, subcomponentTokenizer);
- contentHandler.endElement("", segmentId + "." + fieldId + "." + componentId, null);
+ contentHandler.endElement("", segmentId + "." + fieldId + "." + componentId, "");
} else {
logger.trace("handling component: " + component);
// the naming is SEG..
- contentHandler.startElement("", segmentId + "." + fieldId + "." + componentId, "", null);
+ contentHandler.startElement("", segmentId + "." + fieldId + "." + componentId, "", getEmptyAttributes());
contentHandler.characters(component.toCharArray(), 0, component.length());
contentHandler.endElement("", segmentId + "." + fieldId + "." + componentId, "");
}
@@ -313,7 +312,7 @@ private void handleSubcomponents(ContentHandler contentHandler, String subcompon
if (subcomponent.equals(subcomponentSeparator)) {
if (atLastSubcomponent) {
- contentHandler.startElement("", segmentId + "." + fieldId + "." + componentId + "." + subcomponentId, "", null);
+ contentHandler.startElement("", segmentId + "." + fieldId + "." + componentId + "." + subcomponentId, "", getEmptyAttributes());
contentHandler.characters(EMPTY_CHAR_ARRAY, 0, 0);
contentHandler.endElement("", segmentId + "." + fieldId + "." + componentId + "." + subcomponentId, "");
}
@@ -324,14 +323,14 @@ private void handleSubcomponents(ContentHandler contentHandler, String subcompon
logger.trace("handling subcomponent: " + subcomponentId);
atLastSubcomponent = false;
// the naming is SEG...
- contentHandler.startElement("", segmentId + "." + fieldId + "." + componentId + "." + subcomponentId, "", null);
+ contentHandler.startElement("", segmentId + "." + fieldId + "." + componentId + "." + subcomponentId, "", getEmptyAttributes());
contentHandler.characters(subcomponent.toCharArray(), 0, subcomponent.length());
contentHandler.endElement("", segmentId + "." + fieldId + "." + componentId + "." + subcomponentId, "");
}
}
if (atLastSubcomponent) {
- contentHandler.startElement("", segmentId + "." + fieldId + "." + componentId + "." + subcomponentId, "", null);
+ contentHandler.startElement("", segmentId + "." + fieldId + "." + componentId + "." + subcomponentId, "", getEmptyAttributes());
contentHandler.characters(EMPTY_CHAR_ARRAY, 0, 0);
contentHandler.endElement("", segmentId + "." + fieldId + "." + componentId + "." + subcomponentId, "");
}
diff --git a/server/src/com/mirth/connect/plugins/datatypes/ncpdp/NCPDPReader.java b/server/src/com/mirth/connect/plugins/datatypes/ncpdp/NCPDPReader.java
index cc4a8388fb..6b86b0ed9e 100644
--- a/server/src/com/mirth/connect/plugins/datatypes/ncpdp/NCPDPReader.java
+++ b/server/src/com/mirth/connect/plugins/datatypes/ncpdp/NCPDPReader.java
@@ -1,11 +1,6 @@
-/*
- * Copyright (c) Mirth Corporation. All rights reserved.
- *
- * http://www.mirthcorp.com
- *
- * The software in this package is published under the terms of the MPL license a copy of which has
- * been included with this distribution in the LICENSE.txt file.
- */
+// SPDX-License-Identifier: MPL-2.0
+// SPDX-FileCopyrightText: Mirth Corporation
+// SPDX-FileCopyrightText: 2025 Tony Germano
package com.mirth.connect.plugins.datatypes.ncpdp;
@@ -16,15 +11,15 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
-import org.apache.xerces.parsers.SAXParser;
+import org.openintegrationengine.engine.plugins.datatypes.AbstractXMLReader;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
-public class NCPDPReader extends SAXParser {
+public class NCPDPReader extends AbstractXMLReader {
private Logger logger = LogManager.getLogger(this.getClass());
-
+
private String segmentDelimeter;
private String groupDelimeter;
private String fieldDelimeter;
@@ -38,6 +33,8 @@ public NCPDPReader(String segmentDelimeter, String groupDelimeter, String fieldD
@Override
public void parse(InputSource input) throws SAXException, IOException {
+ ensureHandlerSet();
+
// convert the InputSource to a String and trim the whitespace
String message = IOUtils.toString(input.getCharacterStream()).trim();
@@ -84,11 +81,11 @@ public void parse(InputSource input) throws SAXException, IOException {
if (firstTransaction) {
firstTransaction = false;
- contentHandler.startElement("", "TRANSACTIONS", "", null);
+ contentHandler.startElement("", "TRANSACTIONS", "", getEmptyAttributes());
}
// process a group
- AttributesImpl attr = new AttributesImpl();
+ AttributesImpl attr = getEmptyAttributes();
attr.addAttribute("", "counter", "counter", "", Integer.toString(++groupCounter));
contentHandler.startElement("", "TRANSACTION", "", attr);
inGroup = true;
@@ -129,33 +126,33 @@ private String parseHeader(String message, ContentHandler contentHandler) throws
version = header.substring(6, 8);
headerElementName = "NCPDP_" + version + "_" + transactionName + "_Request";
- contentHandler.startElement("", headerElementName, "", null);
- contentHandler.startElement("", "TransactionHeaderRequest", "", null);
- contentHandler.startElement("", "BinNumber", "", null);
+ contentHandler.startElement("", headerElementName, "", getEmptyAttributes());
+ contentHandler.startElement("", "TransactionHeaderRequest", "", getEmptyAttributes());
+ contentHandler.startElement("", "BinNumber", "", getEmptyAttributes());
contentHandler.characters(header.toCharArray(), 0, 6);
contentHandler.endElement("", "BinNumber", "");
- contentHandler.startElement("", "VersionReleaseNumber", "", null);
+ contentHandler.startElement("", "VersionReleaseNumber", "", getEmptyAttributes());
contentHandler.characters(header.toCharArray(), 6, 2);
contentHandler.endElement("", "VersionReleaseNumber", "");
- contentHandler.startElement("", "TransactionCode", "", null);
+ contentHandler.startElement("", "TransactionCode", "", getEmptyAttributes());
contentHandler.characters(header.toCharArray(), 8, 2);
contentHandler.endElement("", "TransactionCode", "");
- contentHandler.startElement("", "ProcessorControlNumber", "", null);
+ contentHandler.startElement("", "ProcessorControlNumber", "", getEmptyAttributes());
contentHandler.characters(header.toCharArray(), 10, 10);
contentHandler.endElement("", "ProcessorControlNumber", "");
- contentHandler.startElement("", "TransactionCount", "", null);
+ contentHandler.startElement("", "TransactionCount", "", getEmptyAttributes());
contentHandler.characters(header.toCharArray(), 20, 1);
contentHandler.endElement("", "TransactionCount", "");
- contentHandler.startElement("", "ServiceProviderIdQualifier", "", null);
+ contentHandler.startElement("", "ServiceProviderIdQualifier", "", getEmptyAttributes());
contentHandler.characters(header.toCharArray(), 21, 2);
contentHandler.endElement("", "ServiceProviderIdQualifier", "");
- contentHandler.startElement("", "ServiceProviderId", "", null);
+ contentHandler.startElement("", "ServiceProviderId", "", getEmptyAttributes());
contentHandler.characters(header.toCharArray(), 23, 15);
contentHandler.endElement("", "ServiceProviderId", "");
- contentHandler.startElement("", "DateOfService", "", null);
+ contentHandler.startElement("", "DateOfService", "", getEmptyAttributes());
contentHandler.characters(header.toCharArray(), 38, 8);
contentHandler.endElement("", "DateOfService", "");
- contentHandler.startElement("", "SoftwareVendorCertificationId", "", null);
+ contentHandler.startElement("", "SoftwareVendorCertificationId", "", getEmptyAttributes());
contentHandler.characters(header.toCharArray(), 46, 10);
contentHandler.endElement("", "SoftwareVendorCertificationId", "");
contentHandler.endElement("", "TransactionHeaderRequest", "");
@@ -164,27 +161,27 @@ private String parseHeader(String message, ContentHandler contentHandler) throws
version = header.substring(0, 2);
headerElementName = "NCPDP_" + version + "_" + transaction + "_Response";
- contentHandler.startElement("", headerElementName, "", null);
- contentHandler.startElement("", "TransactionHeaderResponse", "", null);
- contentHandler.startElement("", "VersionReleaseNumber", "", null);
+ contentHandler.startElement("", headerElementName, "", getEmptyAttributes());
+ contentHandler.startElement("", "TransactionHeaderResponse", "", getEmptyAttributes());
+ contentHandler.startElement("", "VersionReleaseNumber", "", getEmptyAttributes());
contentHandler.characters(header.toCharArray(), 0, 2);
contentHandler.endElement("", "VersionReleaseNumber", "");
- contentHandler.startElement("", "TransactionCode", "", null);
+ contentHandler.startElement("", "TransactionCode", "", getEmptyAttributes());
contentHandler.characters(header.toCharArray(), 2, 2);
contentHandler.endElement("", "TransactionCode", "");
- contentHandler.startElement("", "TransactionCount", "", null);
+ contentHandler.startElement("", "TransactionCount", "", getEmptyAttributes());
contentHandler.characters(header.toCharArray(), 4, 1);
contentHandler.endElement("", "TransactionCount", "");
- contentHandler.startElement("", "HeaderResponseStatus", "", null);
+ contentHandler.startElement("", "HeaderResponseStatus", "", getEmptyAttributes());
contentHandler.characters(header.toCharArray(), 5, 1);
contentHandler.endElement("", "HeaderResponseStatus", "");
- contentHandler.startElement("", "ServiceProviderIdQualifier", "", null);
+ contentHandler.startElement("", "ServiceProviderIdQualifier", "", getEmptyAttributes());
contentHandler.characters(header.toCharArray(), 6, 2);
contentHandler.endElement("", "ServiceProviderIdQualifier", "");
- contentHandler.startElement("", "ServiceProviderId", "", null);
+ contentHandler.startElement("", "ServiceProviderId", "", getEmptyAttributes());
contentHandler.characters(header.toCharArray(), 8, 15);
contentHandler.endElement("", "ServiceProviderId", "");
- contentHandler.startElement("", "DateOfService", "", null);
+ contentHandler.startElement("", "DateOfService", "", getEmptyAttributes());
contentHandler.characters(header.toCharArray(), 23, 8);
contentHandler.endElement("", "DateOfService", "");
contentHandler.endElement("", "TransactionHeaderResponse", "");
@@ -222,7 +219,7 @@ private void parseSegment(String segment, ContentHandler contentHandler) throws
subSegment = segment.substring(fieldDelimeterIndex + fieldDelimeter.length(), segment.length());
}
- contentHandler.startElement("", NCPDPReference.getInstance().getSegment(segmentId, version), "", null);
+ contentHandler.startElement("", NCPDPReference.getInstance().getSegment(segmentId, version), "", getEmptyAttributes());
while (hasMoreFields) {
fieldDelimeterIndex = subSegment.indexOf(fieldDelimeter);
@@ -261,20 +258,20 @@ private void parseSegment(String segment, ContentHandler contentHandler) throws
}
inCounter = true;
- AttributesImpl attr = new AttributesImpl();
+ AttributesImpl attr = getEmptyAttributes();
attr.addAttribute("", "counter", "counter", "", fieldMessage);
contentHandler.startElement("", fieldDescription, "", attr);
fieldStack.push(fieldDescription);
} else if (fieldDescription.endsWith("Count")) {
// count field, add complex element
inCount = true;
- AttributesImpl attr = new AttributesImpl();
+ AttributesImpl attr = getEmptyAttributes();
attr.addAttribute("", fieldDescription, fieldDescription, "", fieldMessage);
// start the repeating field element
contentHandler.startElement("", fieldDescription, "", attr);
fieldStack.push(fieldDescription);
} else {
- contentHandler.startElement("", fieldDescription, "", null);
+ contentHandler.startElement("", fieldDescription, "", getEmptyAttributes());
contentHandler.characters(fieldMessage.toCharArray(), 0, fieldMessage.length());
contentHandler.endElement("", fieldDescription, "");
}
@@ -291,4 +288,4 @@ private void parseSegment(String segment, ContentHandler contentHandler) throws
private boolean isRepeatingField(String fieldDescription) {
return NCPDPReference.getInstance().isRepeatingField(fieldDescription, version);
}
-}
\ No newline at end of file
+}
diff --git a/server/src/org/openintegrationengine/engine/plugins/datatypes/AbstractXMLReader.java b/server/src/org/openintegrationengine/engine/plugins/datatypes/AbstractXMLReader.java
new file mode 100644
index 0000000000..9a77f9514a
--- /dev/null
+++ b/server/src/org/openintegrationengine/engine/plugins/datatypes/AbstractXMLReader.java
@@ -0,0 +1,275 @@
+// SPDX-License-Identifier: MPL-2.0
+// SPDX-FileCopyrightText: 2025 Tony Germano
+
+package org.openintegrationengine.engine.plugins.datatypes;
+
+import java.io.IOException;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.DTDHandler;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXNotRecognizedException;
+import org.xml.sax.SAXNotSupportedException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * A base class for custom XMLReaders used in data type plugins.
+ *
+ * This class handles the boilerplate requirements of the {@link XMLReader} interface,
+ * such as storing handler references and managing standard features/properties.
+ * Subclasses only need to implement the specific {@link #parse(InputSource)} logic.
+ *
+ */
+public abstract class AbstractXMLReader implements XMLReader {
+
+ /** The ContentHandler to receive SAX events. */
+ protected ContentHandler contentHandler;
+
+ /** The ErrorHandler to receive error notifications. */
+ protected ErrorHandler errorHandler;
+
+ /** The DTDHandler to receive DTD events (rarely used for data types). */
+ protected DTDHandler dtdHandler;
+
+ /** The EntityResolver to resolve external entities. */
+ protected EntityResolver entityResolver;
+
+ /** Reusable instance that is cleared on each call of the accessor */
+ private final AttributesImpl emptyAttributes = new AttributesImpl();
+
+ /**
+ * Helper method for subclasses to ensure the ContentHandler is configured.
+ *
+ * Subclasses should call this at the beginning of their {@code parse} method.
+ *
+ *
+ * @throws SAXException if the ContentHandler has not been set.
+ */
+ protected void ensureHandlerSet() throws SAXException {
+ if (contentHandler == null) {
+ throw new SAXException("ContentHandler not set");
+ }
+ }
+
+ /**
+ * Helper method for subclasses to get an empty instance of {@link AttributesImpl}.
+ *
+ * This method reuses the same instance of {@link AttributesImpl} and clears it
+ * before use.
+ *
+ *
+ * @return An empty instance of {@link AttributesImpl}
+ */
+ protected AttributesImpl getEmptyAttributes() {
+ emptyAttributes.clear();
+ return emptyAttributes;
+ }
+
+ /**
+ * Parse an XML document from the given input source.
+ *
+ * Subclasses must implement this method to translate their specific data format
+ * into SAX events on the {@code contentHandler}.
+ *
+ *
+ * @param input The input source for the top-level of the XML document.
+ * @throws IOException If an I/O error occurs.
+ * @throws SAXException If any SAX errors occur during processing.
+ */
+ @Override
+ public abstract void parse(InputSource input) throws IOException, SAXException;
+
+ /**
+ * Parse an XML document from a system identifier (URI).
+ *
+ * This implementation delegates to {@link #parse(InputSource)}.
+ *
+ *
+ * @param systemId The system identifier (URI).
+ * @throws IOException If an I/O error occurs.
+ * @throws SAXException If any SAX errors occur during processing.
+ */
+ @Override
+ public void parse(String systemId) throws IOException, SAXException {
+ parse(new InputSource(systemId));
+ }
+
+ // ---------------------------------------------------------
+ // Standard Handler Getters/Setters
+ // ---------------------------------------------------------
+
+ /**
+ * Return the current content handler.
+ *
+ * @return The current content handler, or null if none has been registered.
+ */
+ @Override
+ public ContentHandler getContentHandler() {
+ return contentHandler;
+ }
+
+ /**
+ * Allow an application to register a content event handler.
+ *
+ * @param handler The content handler.
+ */
+ @Override
+ public void setContentHandler(ContentHandler handler) {
+ this.contentHandler = handler;
+ }
+
+ /**
+ * Return the current error handler.
+ *
+ * @return The current error handler, or null if none has been registered.
+ */
+ @Override
+ public ErrorHandler getErrorHandler() {
+ return errorHandler;
+ }
+
+ /**
+ * Allow an application to register an error event handler.
+ *
+ * @param handler The error handler.
+ */
+ @Override
+ public void setErrorHandler(ErrorHandler handler) {
+ this.errorHandler = handler;
+ }
+
+ /**
+ * Return the current DTD handler.
+ *
+ * @return The current DTD handler, or null if none has been registered.
+ */
+ @Override
+ public DTDHandler getDTDHandler() {
+ return dtdHandler;
+ }
+
+ /**
+ * Allow an application to register a DTD event handler.
+ *
+ * @param handler The DTD handler.
+ */
+ @Override
+ public void setDTDHandler(DTDHandler handler) {
+ this.dtdHandler = handler;
+ }
+
+ /**
+ * Return the current entity resolver.
+ *
+ * @return The current entity resolver, or null if none has been registered.
+ */
+ @Override
+ public EntityResolver getEntityResolver() {
+ return entityResolver;
+ }
+
+ /**
+ * Allow an application to register an entity resolver.
+ *
+ * @param resolver The entity resolver.
+ */
+ @Override
+ public void setEntityResolver(EntityResolver resolver) {
+ this.entityResolver = resolver;
+ }
+
+ // ---------------------------------------------------------
+ // Strict Feature/Property Compliance
+ // ---------------------------------------------------------
+
+ /**
+ * Look up the value of a feature flag.
+ *
+ * This implementation enforces mandatory SAX2 features:
+ *
+ * - {@code http://xml.org/sax/features/namespaces}: Always returns {@code true}.
+ * - {@code http://xml.org/sax/features/namespace-prefixes}: Always returns {@code false}.
+ *
+ * All other features throw {@link SAXNotRecognizedException}.
+ *
+ *
+ * @param name The feature name, which is a fully-qualified URI.
+ * @return The current value of the feature (true or false).
+ * @throws SAXNotRecognizedException If the feature value can't be assigned or
+ * retrieved.
+ * @throws SAXNotSupportedException When the feature name is recognized but its
+ * value cannot be determined at this time.
+ */
+ @Override
+ public boolean getFeature(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
+ if ("http://xml.org/sax/features/namespaces".equals(name)) {
+ return true;
+ }
+ else if ("http://xml.org/sax/features/namespace-prefixes".equals(name)) {
+ return false;
+ }
+ throw new SAXNotRecognizedException("Feature not recognized: " + name);
+ }
+
+ /**
+ * Set the value of a feature flag.
+ *
+ * This implementation prevents modifying mandatory SAX2 features to ensure the parser
+ * functions correctly with downstream transformers.
+ *
+ *
+ * @param name The feature name, which is a fully-qualified URI.
+ * @param value The requested value of the feature (true or false).
+ * @throws SAXNotRecognizedException If the feature value can't be assigned or retrieved.
+ * @throws SAXNotSupportedException When the feature name is recognized but its
+ * value cannot be set to the requested value.
+ */
+ @Override
+ public void setFeature(String name, boolean value) throws SAXNotRecognizedException, SAXNotSupportedException {
+ if ("http://xml.org/sax/features/namespaces".equals(name)) {
+ if (!value) {
+ throw new SAXNotSupportedException("Cannot disable 'namespaces' feature for this parser");
+ }
+ return;
+ }
+ else if ("http://xml.org/sax/features/namespace-prefixes".equals(name)) {
+ if (value) {
+ throw new SAXNotSupportedException("Cannot enable 'namespace-prefixes' feature for this parser");
+ }
+ return;
+ }
+ throw new SAXNotRecognizedException("Feature not recognized: " + name);
+ }
+
+ /**
+ * Look up the value of a property.
+ *
+ * @param name The property name, which is a fully-qualified URI.
+ * @return The current value of the property.
+ * @throws SAXNotRecognizedException If the property value can't be assigned or retrieved.
+ * @throws SAXNotSupportedException When the property name is recognized but its
+ * value cannot be determined at this time.
+ */
+ @Override
+ public Object getProperty(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
+ throw new SAXNotRecognizedException("Property not recognized: " + name);
+ }
+
+ /**
+ * Set the value of a property.
+ *
+ * @param name The property name, which is a fully-qualified URI.
+ * @param value The requested value for the property.
+ * @throws SAXNotRecognizedException If the property value can't be assigned or retrieved.
+ * @throws SAXNotSupportedException When the property name is recognized but its
+ * value cannot be set to the requested value.
+ */
+ @Override
+ public void setProperty(String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException {
+ throw new SAXNotRecognizedException("Property not recognized: " + name);
+ }
+}
diff --git a/server/test/org/openintegrationengine/engine/plugins/datatypes/AbstractXMLReaderTest.java b/server/test/org/openintegrationengine/engine/plugins/datatypes/AbstractXMLReaderTest.java
new file mode 100644
index 0000000000..bff1664839
--- /dev/null
+++ b/server/test/org/openintegrationengine/engine/plugins/datatypes/AbstractXMLReaderTest.java
@@ -0,0 +1,234 @@
+// SPDX-License-Identifier: MPL-2.0
+// SPDX-FileCopyrightText: 2025 Tony Germano
+
+package org.openintegrationengine.engine.plugins.datatypes;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import java.io.IOException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InOrder;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.DTDHandler;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXNotRecognizedException;
+import org.xml.sax.SAXNotSupportedException;
+
+public class AbstractXMLReaderTest {
+
+ private AbstractXMLReader reader;
+
+ @Before
+ public void setUp() {
+ reader = new TestXMLReader();
+ }
+
+ // ---------------------------------------------------------
+ // Parsing Logic Tests
+ // ---------------------------------------------------------
+
+ @Test
+ public void testParseEmitsSaxEvents() throws Exception {
+ // Mock the ContentHandler to verify events
+ ContentHandler handler = mock(ContentHandler.class);
+ reader.setContentHandler(handler);
+
+ // Execute parse
+ reader.parse(new InputSource());
+
+ // Verify the exact sequence of SAX events was fired
+ InOrder inOrder = inOrder(handler);
+ inOrder.verify(handler).startDocument();
+ inOrder.verify(handler).startElement(eq(""), eq("test"), eq(""), any(Attributes.class));
+ inOrder.verify(handler).characters(eq("value".toCharArray()), eq(0), eq(5));
+ inOrder.verify(handler).endElement(eq(""), eq("test"), eq(""));
+ inOrder.verify(handler).endDocument();
+ }
+
+ @Test
+ public void testParseStringDelegatesToInputSource() throws IOException, SAXException {
+ // Spy on the reader to verify method calls
+ AbstractXMLReader spyReader = spy(new TestXMLReader());
+ String testUri = "file:///test.xml";
+
+ // Set a dummy handler so ensuringHandlerSet doesn't fail
+ spyReader.setContentHandler(mock(ContentHandler.class));
+
+ spyReader.parse(testUri);
+
+ // Verify that parse(String) called parse(InputSource)
+ verify(spyReader).parse(any(InputSource.class));
+ }
+
+ // ---------------------------------------------------------
+ // Handler Getter/Setter Tests
+ // ---------------------------------------------------------
+
+ @Test
+ public void testContentHandlerAccessors() {
+ assertNull("Should be null initially", reader.getContentHandler());
+
+ ContentHandler mockHandler = mock(ContentHandler.class);
+ reader.setContentHandler(mockHandler);
+
+ assertSame("Should return the set handler", mockHandler, reader.getContentHandler());
+ }
+
+ @Test
+ public void testErrorHandlerAccessors() {
+ assertNull("Should be null initially", reader.getErrorHandler());
+
+ ErrorHandler mockHandler = mock(ErrorHandler.class);
+ reader.setErrorHandler(mockHandler);
+
+ assertSame("Should return the set handler", mockHandler, reader.getErrorHandler());
+ }
+
+ @Test
+ public void testDTDHandlerAccessors() {
+ assertNull("Should be null initially", reader.getDTDHandler());
+
+ DTDHandler mockHandler = mock(DTDHandler.class);
+ reader.setDTDHandler(mockHandler);
+
+ assertSame("Should return the set handler", mockHandler, reader.getDTDHandler());
+ }
+
+ @Test
+ public void testEntityResolverAccessors() {
+ assertNull("Should be null initially", reader.getEntityResolver());
+
+ EntityResolver mockResolver = mock(EntityResolver.class);
+ reader.setEntityResolver(mockResolver);
+
+ assertSame("Should return the set resolver", mockResolver, reader.getEntityResolver());
+ }
+
+ // ---------------------------------------------------------
+ // Helper Method Tests
+ // ---------------------------------------------------------
+
+ @Test
+ public void testEnsureHandlerSetSuccess() throws SAXException {
+ // Setup: Set a handler
+ reader.setContentHandler(mock(ContentHandler.class));
+
+ // Execute: Should not throw exception
+ reader.ensureHandlerSet();
+ }
+
+ @Test(expected = SAXException.class)
+ public void testEnsureHandlerSetFailure() throws SAXException {
+ // Setup: Ensure handler is null
+ reader.setContentHandler(null);
+
+ // Execute: Should throw SAXException
+ reader.ensureHandlerSet();
+ }
+
+ // ---------------------------------------------------------
+ // Feature Flag Tests
+ // ---------------------------------------------------------
+
+ @Test
+ public void testGetFeatureNamespaces() throws Exception {
+ assertTrue("Namespaces should always be true",
+ reader.getFeature("http://xml.org/sax/features/namespaces"));
+ }
+
+ @Test
+ public void testGetFeatureNamespacePrefixes() throws Exception {
+ assertFalse("Namespace prefixes should always be false",
+ reader.getFeature("http://xml.org/sax/features/namespace-prefixes"));
+ }
+
+ @Test(expected = SAXNotRecognizedException.class)
+ public void testGetFeatureUnknown() throws Exception {
+ reader.getFeature("http://xml.org/sax/features/unknown-feature");
+ }
+
+ @Test
+ public void testSetFeatureNamespacesTrue() throws Exception {
+ // Should succeed (no-op)
+ reader.setFeature("http://xml.org/sax/features/namespaces", true);
+ }
+
+ @Test(expected = SAXNotSupportedException.class)
+ public void testSetFeatureNamespacesFalse() throws Exception {
+ // Should fail - cannot disable namespaces
+ reader.setFeature("http://xml.org/sax/features/namespaces", false);
+ }
+
+ @Test
+ public void testSetFeatureNamespacePrefixesFalse() throws Exception {
+ // Should succeed (no-op)
+ reader.setFeature("http://xml.org/sax/features/namespace-prefixes", false);
+ }
+
+ @Test(expected = SAXNotSupportedException.class)
+ public void testSetFeatureNamespacePrefixesTrue() throws Exception {
+ // Should fail - cannot enable namespace prefixes
+ reader.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
+ }
+
+ @Test(expected = SAXNotRecognizedException.class)
+ public void testSetFeatureUnknown() throws Exception {
+ reader.setFeature("http://xml.org/sax/features/unknown-feature", true);
+ }
+
+ // ---------------------------------------------------------
+ // Property Tests
+ // ---------------------------------------------------------
+
+ @Test(expected = SAXNotRecognizedException.class)
+ public void testGetPropertyUnknown() throws Exception {
+ reader.getProperty("http://xml.org/sax/properties/lexical-handler");
+ }
+
+ @Test(expected = SAXNotRecognizedException.class)
+ public void testSetPropertyUnknown() throws Exception {
+ reader.setProperty("http://xml.org/sax/properties/lexical-handler", new Object());
+ }
+
+ // ---------------------------------------------------------
+ // Concrete Implementation for Testing
+ // ---------------------------------------------------------
+
+ /**
+ * A concrete implementation that simulates parsing a simple XML document:
+ * value
+ */
+ private static class TestXMLReader extends AbstractXMLReader {
+ @Override
+ public void parse(InputSource input) throws IOException, SAXException {
+ // Verify handler is present
+ ensureHandlerSet();
+
+ // Simulate parsing value
+ contentHandler.startDocument();
+
+ contentHandler.startElement("", "test", "", getEmptyAttributes());
+
+ String text = "value";
+ contentHandler.characters(text.toCharArray(), 0, text.length());
+
+ contentHandler.endElement("", "test", "");
+ contentHandler.endDocument();
+ }
+ }
+}