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