001/* 002 * (C) Copyright 2012 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * ataillefer 018 */ 019package org.nuxeo.ecm.diff.service.impl; 020 021import java.io.IOException; 022import java.util.List; 023 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026import org.custommonkey.xmlunit.DetailedDiff; 027import org.custommonkey.xmlunit.Diff; 028import org.custommonkey.xmlunit.Difference; 029import org.custommonkey.xmlunit.ElementNameAndAttributeQualifier; 030import org.custommonkey.xmlunit.NodeDetail; 031import org.custommonkey.xmlunit.XMLUnit; 032import org.nuxeo.ecm.core.api.CoreSession; 033import org.nuxeo.ecm.core.api.DocumentModel; 034import org.nuxeo.ecm.core.api.NuxeoException; 035import org.nuxeo.ecm.core.io.DocumentXMLExporter; 036import org.nuxeo.ecm.diff.model.DocumentDiff; 037import org.nuxeo.ecm.diff.model.impl.DocumentDiffImpl; 038import org.nuxeo.ecm.diff.service.DocumentDiffService; 039import org.nuxeo.runtime.api.Framework; 040import org.xml.sax.InputSource; 041import org.xml.sax.SAXException; 042 043/** 044 * Implementation of DocumentDiffService. 045 * <p> 046 * The diff is made by exporting the documents to XML, then using the Diff feature provided by XMLUnit to get the 047 * differences between the XML exports. 048 * 049 * @author <a href="mailto:[email protected]">Antoine Taillefer</a> 050 */ 051public class DocumentDiffServiceImpl implements DocumentDiffService { 052 053 private static final long serialVersionUID = 9023621903602108068L; 054 055 private static final Log LOGGER = LogFactory.getLog(DocumentDiffServiceImpl.class); 056 057 /** 058 * {@inheritDoc} 059 */ 060 public DocumentDiff diff(CoreSession session, DocumentModel leftDoc, DocumentModel rightDoc) { 061 062 // Input sources to hold XML exports 063 InputSource leftDocXMLInputSource = new InputSource(); 064 InputSource rightDocXMLInputSource = new InputSource(); 065 066 // Export leftDoc and rightDoc to XML 067 exportXML(session, leftDoc, rightDoc, leftDocXMLInputSource, rightDocXMLInputSource); 068 069 // Process the XML diff 070 DetailedDiff detailedDiff = diffXML(leftDocXMLInputSource, rightDocXMLInputSource); 071 072 // Fill in the DocumentDiff object using the result of the detailed diff 073 DocumentDiff docDiff = computeDocDiff(detailedDiff); 074 075 return docDiff; 076 } 077 078 /** 079 * {@inheritDoc} 080 */ 081 public DocumentDiff diff(String leftXML, String rightXML) { 082 083 // Process the XML diff 084 DetailedDiff detailedDiff = diffXML(leftXML, rightXML); 085 086 // Fill in the DocumentDiff object using the result of the detailed diff 087 DocumentDiff docDiff = computeDocDiff(detailedDiff); 088 089 return docDiff; 090 } 091 092 /** 093 * {@inheritDoc} 094 */ 095 public void configureXMLUnit() { 096 097 XMLUnit.setIgnoreWhitespace(true); 098 XMLUnit.setIgnoreDiffBetweenTextAndCDATA(true); 099 XMLUnit.setCompareUnmatched(false); 100 } 101 102 /** 103 * {@inheritDoc} 104 */ 105 public void configureDiff(Diff diff) { 106 107 diff.overrideDifferenceListener(new IgnoreStructuralDifferenceListener()); 108 diff.overrideElementQualifier(new ElementNameAndAttributeQualifier()); 109 } 110 111 /** 112 * Exports leftDoc and rightDoc to XML. 113 * 114 * @param session the session 115 * @param leftDoc the left doc 116 * @param rightDoc the right doc 117 * @param leftDocXMLInputSource the left doc XML input source 118 * @param rightDocXMLInputSource the right doc XML input source 119 */ 120 protected final void exportXML(CoreSession session, DocumentModel leftDoc, DocumentModel rightDoc, 121 InputSource leftDocXMLInputSource, InputSource rightDocXMLInputSource) { 122 123 DocumentXMLExporter docXMLExporter = getDocumentXMLExporter(); 124 125 leftDocXMLInputSource.setByteStream(docXMLExporter.exportXML(leftDoc, session)); 126 rightDocXMLInputSource.setByteStream(docXMLExporter.exportXML(rightDoc, session)); 127 } 128 129 /** 130 * Gets the document XML exporter service. 131 * 132 * @return the document XML exporter 133 */ 134 protected final DocumentXMLExporter getDocumentXMLExporter() { 135 return Framework.getService(DocumentXMLExporter.class); 136 } 137 138 /** 139 * Processes the XML diff using the XMLUnit Diff feature. 140 * 141 * @param leftDocXMLInputSource the left doc XML input source 142 * @param rightDocXMLInputSource the right doc XML input source 143 * @return the detailed diff 144 */ 145 protected final DetailedDiff diffXML(InputSource leftDocXMLInputSource, InputSource rightDocXMLInputSource) 146 { 147 148 DetailedDiff detailedDiff; 149 try { 150 // Configure XMLUnit 151 configureXMLUnit(); 152 // Build diff 153 Diff diff = new Diff(leftDocXMLInputSource, rightDocXMLInputSource); 154 // Configure diff 155 configureDiff(diff); 156 // Build detailed diff 157 detailedDiff = new DetailedDiff(diff); 158 } catch (SAXException | IOException e) { 159 throw new NuxeoException("Error while trying to make a detailed diff between two documents.", e); 160 } 161 return detailedDiff; 162 } 163 164 /** 165 * Processes the XML diff using the XMLUnit Diff feature. 166 * 167 * @param leftXML the left xml 168 * @param rightXML the right xml 169 * @return the detailed diff 170 */ 171 protected final DetailedDiff diffXML(String leftXML, String rightXML) { 172 173 DetailedDiff detailedDiff; 174 try { 175 // Configure XMLUnit 176 configureXMLUnit(); 177 // Build diff 178 Diff diff = new Diff(leftXML, rightXML); 179 // Configure diff 180 configureDiff(diff); 181 // Build detailed diff 182 detailedDiff = new DetailedDiff(diff); 183 } catch (SAXException | IOException e) { 184 throw new NuxeoException("Error while trying to make a detailed diff between two XML strings.", e); 185 } 186 return detailedDiff; 187 } 188 189 /** 190 * Computes the doc diff. 191 * 192 * @param detailedDiff the detailed diff 193 * @return the document diff 194 */ 195 @SuppressWarnings("unchecked") 196 protected final DocumentDiff computeDocDiff(DetailedDiff detailedDiff) { 197 198 // Document diff object 199 DocumentDiff docDiff = new DocumentDiffImpl(); 200 201 // Iterate on differences 202 List<Difference> differences = detailedDiff.getAllDifferences(); 203 LOGGER.debug(String.format("Found %d differences.", differences.size())); 204 205 int fieldDifferenceCount = 0; 206 for (Difference difference : differences) { 207 208 // Control node <=> left doc node 209 NodeDetail controlNodeDetail = difference.getControlNodeDetail(); 210 // Test node <=> right doc node 211 NodeDetail testNodeDetail = difference.getTestNodeDetail(); 212 213 if (controlNodeDetail != null && testNodeDetail != null) { 214 215 boolean fieldDiffFound = FieldDiffHelper.computeFieldDiff(docDiff, controlNodeDetail, testNodeDetail, 216 fieldDifferenceCount, difference); 217 if (fieldDiffFound) { 218 fieldDifferenceCount++; 219 } 220 } 221 } 222 LOGGER.debug(String.format("Found %d field differences.", fieldDifferenceCount)); 223 224 return docDiff; 225 } 226 227}