NIFI-7991 Flow Configuration History displays "annotation data not found/available" from "Advanced" changes

Signed-off-by: Matthew Burgess <mattyb149@apache.org>

This closes #4668
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ProcessorAuditor.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ProcessorAuditor.java
index eeb7bf6..e502a9a 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ProcessorAuditor.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ProcessorAuditor.java
@@ -39,7 +39,15 @@
 import org.aspectj.lang.annotation.Aspect;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
 
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.io.StringReader;
 import java.text.Collator;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -50,6 +58,7 @@
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * Audits processor creation/removal and configuration changes.
@@ -163,11 +172,46 @@
                             oldValue = "********";
                         }
                     } else if (ANNOTATION_DATA.equals(property)) {
-                        if (newValue != null) {
-                            newValue = "<annotation data not shown>";
-                        }
-                        if (oldValue != null) {
-                            oldValue = "<annotation data not shown>";
+                        if (newValue != null && oldValue != null) {
+
+                            try {
+
+                                InputSource is = new InputSource();
+                                is.setCharacterStream(new StringReader(newValue));
+                                DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
+                                DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
+                                Document doc = dBuilder.parse(is);
+                                NodeList nList = doc.getChildNodes();
+                                final Map<String, Node> xmlDumpNew = new HashMap<>();
+                                getItemPaths(nList, ""+doc.getNodeName(), xmlDumpNew);
+                                is.setCharacterStream(new StringReader(oldValue));
+                                doc = dBuilder.parse(is);
+                                nList = doc.getChildNodes();
+                                final Map<String, Node> xmlDumpOld = new HashMap<>();
+                                getItemPaths(nList, ""+doc.getNodeName(), xmlDumpOld);
+                                Map<String, Object> xmlDumpSame = new HashMap<>();
+                                xmlDumpNew.forEach((k, v) -> {
+                                            if (xmlDumpOld.containsKey(k)) {
+                                                xmlDumpSame.put(k, v);
+                                            }
+                                        }
+                                );
+                                xmlDumpSame.forEach((k, v) -> {
+                                            xmlDumpNew.remove(k);
+                                            xmlDumpOld.remove(k);
+                                        }
+                                );
+
+                                AtomicReference<String> oldReference = new AtomicReference<>("");
+                                AtomicReference<String> newReference = new AtomicReference<>("");
+
+                                xmlDumpNew.forEach((k, v) -> newReference.set(newReference.get() + ":" + k + System.lineSeparator()));
+                                xmlDumpOld.forEach((k, v) -> oldReference.set(oldReference.get() + ":" + k + System.lineSeparator()));
+                                newValue = newReference.get();
+                                oldValue = oldReference.get();
+
+                            } catch (Exception ignore) { //Not valid XML, so treat as String, no change
+                            }
                         }
                     }
 
@@ -400,4 +444,39 @@
         return specDescriptor;
     }
 
+    /**
+     * Gets Item Paths and set path and node in Map map
+     * @param nl NodeList to generate path
+     * @param path String path to ParentNode
+     * @param map  Map of path to node, and node reference
+     */
+    private void getItemPaths(NodeList nl, String path, Map<String,Node> map){
+        if(nl!=null) {
+
+
+            for (int i = 0; i < nl.getLength(); i++) {
+                Node n;
+                if (( n = nl.item(i)) != null) {
+                    if(n.getNodeType() == Node.ELEMENT_NODE || n.getNodeType() == Node.TEXT_NODE) {
+                        if(n.hasChildNodes()){
+                           if(n.getNodeType() == Node.ELEMENT_NODE) {
+                                getItemPaths(n.getChildNodes(), path + ":" + n.getNodeName(), map);
+                           }
+                        }
+                        if(!n.hasChildNodes()) {
+                            map.put(path + ":" + n.getNodeName().trim()+":"+n.getNodeValue(), n);
+                        }
+
+                        if (n.hasAttributes()) {
+                            NamedNodeMap na = n.getAttributes();
+                            for (int j = 0; j < na.getLength(); j++) {
+                                map.put(path + ":" + n.getNodeName() + ":" + na.item(j).getNodeName().trim()+":"+na.item(j).getNodeValue(), n);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+    }
 }