SANTUARIO-468 - How to add generated signature element deep inside desired tag in XML through stax implementation. Thanks to Manjesh Kumar for the initial patch.

git-svn-id: https://svn.apache.org/repos/asf/santuario/xml-security-java/trunk@1817215 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/xml/security/stax/ext/XMLSecurityProperties.java b/src/main/java/org/apache/xml/security/stax/ext/XMLSecurityProperties.java
index a191c99..6ba3c2f 100644
--- a/src/main/java/org/apache/xml/security/stax/ext/XMLSecurityProperties.java
+++ b/src/main/java/org/apache/xml/security/stax/ext/XMLSecurityProperties.java
@@ -76,6 +76,9 @@
     private boolean signatureGenerateIds = true;
     private boolean signatureIncludeDigestTransform = true;
 
+    private QName signaturePositionQName;
+    private boolean signaturePositionStart = false;
+
     public XMLSecurityProperties() {
     }
 
@@ -112,6 +115,16 @@
         this.keyNameMap.putAll(xmlSecurityProperties.keyNameMap);
         this.signatureGenerateIds = xmlSecurityProperties.signatureGenerateIds;
         this.signatureIncludeDigestTransform = xmlSecurityProperties.signatureIncludeDigestTransform;
+        this.signaturePositionQName = xmlSecurityProperties.signaturePositionQName;
+        this.signaturePositionStart = xmlSecurityProperties.signaturePositionStart;
+    }
+
+    public boolean isSignaturePositionStart() {
+        return signaturePositionStart;
+    }
+
+    public void setSignaturePositionStart(boolean signaturePositionStart) {
+        this.signaturePositionStart = signaturePositionStart;
     }
 
     public SecurityTokenConstants.KeyIdentifier getSignatureKeyIdentifier() {
@@ -493,4 +506,12 @@
     public void setSignatureIncludeDigestTransform(boolean signatureIncludeDigestTransform) {
         this.signatureIncludeDigestTransform = signatureIncludeDigestTransform;
     }
+
+    public QName getSignaturePositionQName() {
+        return signaturePositionQName;
+    }
+
+    public void setSignaturePositionQName(QName signaturePositionQName) {
+        this.signaturePositionQName = signaturePositionQName;
+    }
 }
diff --git a/src/main/java/org/apache/xml/security/stax/impl/processor/output/XMLSignatureEndingOutputProcessor.java b/src/main/java/org/apache/xml/security/stax/impl/processor/output/XMLSignatureEndingOutputProcessor.java
index 1e3f9ff..9da34c6 100644
--- a/src/main/java/org/apache/xml/security/stax/impl/processor/output/XMLSignatureEndingOutputProcessor.java
+++ b/src/main/java/org/apache/xml/security/stax/impl/processor/output/XMLSignatureEndingOutputProcessor.java
@@ -23,6 +23,7 @@
 import java.util.Deque;
 import java.util.List;
 
+import javax.xml.namespace.QName;
 import javax.xml.stream.XMLStreamException;
 
 import org.apache.xml.security.exceptions.XMLSecurityException;
@@ -78,15 +79,6 @@
             OutputProcessorChain outputProcessorChain, Deque<XMLSecEvent> xmlSecEventDeque)
             throws XMLStreamException, XMLSecurityException {
 
-        // @see SANTUARIO-405
-        // Enhances SANTUARIO-324
-        // Output the signature at a specific position.
-        // By default, this is just after the root element
-        int signaturePosition = getSecurityProperties().getSignaturePosition();
-        if (signaturePosition < 0) {
-            signaturePosition = 0;
-        }
-
         // forward to the root element and output it
         XMLSecEvent xmlSecEvent = xmlSecEventDeque.pop();
         while (!xmlSecEvent.isStartElement()) {
@@ -99,25 +91,57 @@
 
         // search the specified position
         int depth = 0;
-        int position = 0;
-        while (position != signaturePosition) {
-            xmlSecEvent = xmlSecEventDeque.pop();
+        QName signaturePositionQName = getSecurityProperties().getSignaturePositionQName();
+        boolean start = getSecurityProperties().isSignaturePositionStart();
+        if (signaturePositionQName != null) {
+            while (!xmlSecEventDeque.isEmpty()
+                && !(start && xmlSecEvent.isStartElement() && xmlSecEvent.asStartElement().getName().equals(signaturePositionQName)
+                    || !start && xmlSecEvent.isEndElement() && xmlSecEvent.asEndElement().getName().equals(signaturePositionQName))) {
+                xmlSecEvent = xmlSecEventDeque.pop();
 
-            if (xmlSecEvent.isStartElement()) {
-                depth++;
-            } else if (xmlSecEvent.isEndElement()) {
-                depth--;
-                if (depth == 0) {
-                    position++;
-                } else if (depth < 0) {
-                    // root-end-element reached
-                    xmlSecEventDeque.push(xmlSecEvent);
-                    break;
+                if (xmlSecEvent.isStartElement()) {
+                    depth++;
+                } else if (xmlSecEvent.isEndElement()) {
+                    depth--;
+                    if (depth < 0) {
+                        // root-end-element reached
+                        xmlSecEventDeque.push(xmlSecEvent);
+                        break;
+                    }
                 }
-            }
 
-            outputProcessorChain.reset();
-            outputProcessorChain.processEvent(xmlSecEvent);
+                outputProcessorChain.reset();
+                outputProcessorChain.processEvent(xmlSecEvent);
+            }
+        } else {
+            // @see SANTUARIO-405
+            // Enhances SANTUARIO-324
+            // Output the signature at a specific position.
+            // By default, this is just after the root element
+            int signaturePosition = getSecurityProperties().getSignaturePosition();
+            if (signaturePosition < 0) {
+                signaturePosition = 0;
+            }
+            int position = 0;
+            while (position != signaturePosition) {
+                xmlSecEvent = xmlSecEventDeque.pop();
+
+                if (xmlSecEvent.isStartElement()) {
+                    depth++;
+                } else if (xmlSecEvent.isEndElement()) {
+                    depth--;
+                    if (depth == 0) {
+                        position++;
+                    } else if (depth < 0) {
+                        // root-end-element reached
+                        xmlSecEventDeque.push(xmlSecEvent);
+                        break;
+                    }
+                }
+
+                outputProcessorChain.reset();
+                outputProcessorChain.processEvent(xmlSecEvent);
+            }
         }
 
         //...then call super to append the signature and flush the rest
diff --git a/src/test/java/org/apache/xml/security/test/stax/signature/SignatureCreationTest.java b/src/test/java/org/apache/xml/security/test/stax/signature/SignatureCreationTest.java
index a33ba56..d117ccc 100644
--- a/src/test/java/org/apache/xml/security/test/stax/signature/SignatureCreationTest.java
+++ b/src/test/java/org/apache/xml/security/test/stax/signature/SignatureCreationTest.java
@@ -296,7 +296,20 @@
         signAtSpecificPosition(999);
     }
 
+    @Test
+    public void testSignAtSpecificPositionViaQName() throws Exception {
+        signAtSpecificPosition(0, new QName("urn:example:po", "PurchaseOrder"), true);
+        signAtSpecificPosition(0, new QName("urn:example:po", "Items"), true);
+        signAtSpecificPosition(0, new QName("urn:example:po", "Items"), false);
+        signAtSpecificPosition(0, new QName("urn:example:po", "ShippingAddress"), true);
+        signAtSpecificPosition(0, new QName("urn:example:po", "ShippingAddress"), false);
+    }
+
     private void signAtSpecificPosition(int position) throws Exception {
+        signAtSpecificPosition(position, null, false);
+    }
+
+    private void signAtSpecificPosition(int position, QName positionQName, boolean start) throws Exception {
         // Set up the Configuration
         XMLSecurityProperties properties = new XMLSecurityProperties();
         List<XMLSecurityConstants.Action> actions = new ArrayList<XMLSecurityConstants.Action>();
@@ -305,6 +318,8 @@
 
         // Specify the signature position
         properties.setSignaturePosition(position);
+        properties.setSignaturePositionQName(positionQName);
+        properties.setSignaturePositionStart(start);
 
         // Set the key up
         KeyStore keyStore = KeyStore.getInstance("jks");
@@ -340,7 +355,7 @@
         XmlReaderToWriter.writeAll(xmlStreamReader, xmlStreamWriter);
         xmlStreamWriter.close();
 
-        //System.out.println("Got:\n" + new String(baos.toByteArray(), StandardCharsets.UTF_8.name()));
+        // System.out.println("Got:\n" + new String(baos.toByteArray(), StandardCharsets.UTF_8.name()));
 
         Document document = null;
         try (InputStream is = new ByteArrayInputStream(baos.toByteArray())) {
@@ -350,15 +365,32 @@
         //find first child element:
         Node childNode = XMLUtils.getNextElement(document.getDocumentElement().getFirstChild());
 
-        int expectedPosition = position < 0 ? 0 : position;
-        int curPos = 0;
-        while (curPos != expectedPosition) {
-            Node node = XMLUtils.getNextElement(childNode.getNextSibling());
-            curPos++;
-            if (node != null) {
-                childNode = node;
-            } else {
-                break;
+        if (positionQName != null) {
+            // Find the Signature node inside the desired QName
+            String localName = positionQName.getLocalPart();
+            if (!"PurchaseOrder".equals(localName)) {
+                String namespace = positionQName.getNamespaceURI();
+                while (childNode != null && !(childNode.getLocalName().equals(localName)
+                    && childNode.getNamespaceURI().equals(namespace))) {
+                    childNode = XMLUtils.getNextElement(childNode.getNextSibling());
+                }
+                if (start) {
+                    childNode = childNode.getFirstChild();
+                } else {
+                    childNode = childNode.getNextSibling();
+                }
+            }
+        } else {
+            int expectedPosition = position < 0 ? 0 : position;
+            int curPos = 0;
+            while (curPos != expectedPosition) {
+                Node node = XMLUtils.getNextElement(childNode.getNextSibling());
+                curPos++;
+                if (node != null) {
+                    childNode = node;
+                } else {
+                    break;
+                }
             }
         }