package com.celements.copydoc; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xwiki.component.annotation.Component; import org.xwiki.component.annotation.Requirement; import org.xwiki.model.reference.DocumentReference; import com.celements.model.access.IModelAccessFacade; import com.celements.model.access.exception.ClassDocumentLoadException; import com.celements.model.access.exception.DocumentSaveException; import com.celements.web.service.IWebUtilsService; import com.google.common.base.Objects; import com.google.common.collect.Iterables; import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.objects.BaseObject; @Component public class CopyDocumentService implements ICopyDocumentRole { private static final Logger LOGGER = LoggerFactory.getLogger(CopyDocumentService.class); @Requirement private IModelAccessFacade modelAccess; @Requirement private IWebUtilsService webUtilsService; @Override public boolean check(XWikiDocument doc1, XWikiDocument doc2) { return check(doc1, doc2, null); } @Override public boolean check(XWikiDocument doc1, XWikiDocument doc2, Collection<BaseObject> toIgnore) { try { return copyInternal(doc1, doc2, toIgnore, false); } catch (ClassDocumentLoadException exc) { LOGGER.error("should not happen", exc); // since set is false throw new RuntimeException("ClassDocumentLoadException should not happen"); } } @Override public boolean copyAndSave(XWikiDocument srcDoc, XWikiDocument trgDoc) throws ClassDocumentLoadException, DocumentSaveException { return copyAndSave(srcDoc, trgDoc, null); } @Override public boolean copyAndSave(XWikiDocument srcDoc, XWikiDocument trgDoc, Set<BaseObject> toIgnore) throws ClassDocumentLoadException, DocumentSaveException { boolean hasChanged = copy(srcDoc, trgDoc, toIgnore); if (hasChanged) { modelAccess.saveDocument(trgDoc, "copy from " + srcDoc); } return hasChanged; } @Override public boolean copy(XWikiDocument srcDoc, XWikiDocument trgDoc) throws ClassDocumentLoadException { return copy(srcDoc, trgDoc, null); } @Override public boolean copy(XWikiDocument srcDoc, XWikiDocument trgDoc, Collection<BaseObject> toIgnore) throws ClassDocumentLoadException { return copyInternal(srcDoc, trgDoc, toIgnore, true); } private boolean copyInternal(XWikiDocument srcDoc, XWikiDocument trgDoc, Collection<BaseObject> toIgnore, boolean set) throws ClassDocumentLoadException { boolean hasChanged = false; hasChanged |= copyDocFields(srcDoc, trgDoc, set); hasChanged |= copyObjects(srcDoc, trgDoc, toIgnore, set); LOGGER.info("for source '" + srcDoc + "', target '" + trgDoc + "', set '" + set + "' has changed: " + hasChanged); return hasChanged; } private boolean copyDocFields(XWikiDocument srcDoc, XWikiDocument trgDoc, boolean set) { boolean hasChanged = false; String srcLang = srcDoc.getLanguage(); String trgLang = trgDoc.getLanguage(); if (!Objects.equal(srcLang, trgLang)) { if (set) { trgDoc.setLanguage(srcLang); } hasChanged = true; LOGGER.trace("for doc '" + trgDoc + "' language changed from '" + trgLang + "' to '" + srcLang + "'"); } int srcTransl = srcDoc.getTranslation(); int trgTransl = trgDoc.getTranslation(); if (!Objects.equal(srcTransl, trgTransl)) { if (set) { trgDoc.setTranslation(srcTransl); } hasChanged = true; LOGGER.trace("for doc '" + trgDoc + "' translation changed from '" + trgTransl + "' to '" + srcTransl + "'"); } String srcTitle = srcDoc.getTitle(); String trgTitle = trgDoc.getTitle(); if (!Objects.equal(srcTitle, trgTitle)) { if (set) { trgDoc.setTitle(srcTitle); } hasChanged = true; LOGGER.trace("for doc '" + trgDoc + "' title changed from '" + trgTitle + "' to '" + srcTitle + "'"); } String srcContent = srcDoc.getContent(); String trgContent = trgDoc.getContent(); if (!Objects.equal(srcContent, trgContent)) { if (set) { trgDoc.setContent(srcContent); } hasChanged = true; LOGGER.trace("for doc '" + trgDoc + "' content changed from '" + trgContent + "' to '" + srcContent + "'"); } return hasChanged; } boolean copyObjects(XWikiDocument srcDoc, XWikiDocument trgDoc, Collection<BaseObject> toIgnore, boolean set) throws ClassDocumentLoadException { boolean hasChanged = false; for (DocumentReference classRef : getAllClassRefs(srcDoc, trgDoc)) { List<BaseObject> srcObjs = getXObjects(srcDoc, classRef, toIgnore); List<BaseObject> trgObjs = getXObjects(trgDoc, classRef, toIgnore); hasChanged |= createOrUpdateObjects(trgDoc, srcObjs, trgObjs, set); hasChanged |= (set && modelAccess.removeXObjects(trgDoc, trgObjs)) || (!set && !trgObjs.isEmpty()); } return hasChanged; } Set<DocumentReference> getAllClassRefs(XWikiDocument srcDoc, XWikiDocument trgDoc) { Set<DocumentReference> ret = new HashSet<>(); for (DocumentReference classRef : Iterables.concat(modelAccess.getXObjects(srcDoc).keySet(), modelAccess.getXObjects(trgDoc).keySet())) { ret.add(webUtilsService.checkWikiRef(classRef, trgDoc)); } return ret; } List<BaseObject> getXObjects(XWikiDocument doc, DocumentReference classRef, Collection<BaseObject> toIgnore) { List<BaseObject> ret = new ArrayList<>(modelAccess.getXObjects(doc, classRef)); if (toIgnore != null) { ret.removeAll(toIgnore); } return ret; } boolean createOrUpdateObjects(XWikiDocument doc, List<BaseObject> srcObjs, List<BaseObject> trgObjs, boolean set) throws ClassDocumentLoadException { boolean hasChanged = false; Iterator<BaseObject> trgObjIter = trgObjs.iterator(); for (BaseObject srcObj : srcObjs) { BaseObject trgObj = (trgObjIter.hasNext() ? trgObjIter.next() : null); if (trgObj != null) { trgObjIter.remove(); } else { DocumentReference classRef = srcObj.getXClassReference(); if (set) { trgObj = modelAccess.newXObject(doc, classRef); } hasChanged = true; LOGGER.trace("for doc '" + doc + "' new object for '" + classRef + "'"); } if (trgObj != null) { hasChanged |= copyObject(srcObj, trgObj, set); } } return hasChanged; } @Override public boolean checkObject(BaseObject obj1, BaseObject obj2) { return copyObject(obj1, obj2, false); } @Override public boolean copyObject(BaseObject srcObj, BaseObject trgObj) { return copyObject(srcObj, trgObj, true); } boolean copyObject(BaseObject srcObj, BaseObject trgObj, boolean set) { boolean hasChanged = false; Set<String> srcProps = srcObj.getPropertyList(); Set<String> trgProps = new HashSet<>(trgObj.getPropertyList()); for (String name : srcProps) { Object srcVal = modelAccess.getProperty(srcObj, name); Object trgVal = modelAccess.getProperty(trgObj, name); if (!Objects.equal(srcVal, trgVal)) { if (set) { modelAccess.setProperty(trgObj, name, srcVal); } hasChanged = true; LOGGER.trace("for doc '" + trgObj.getDocumentReference() + "' field '" + name + "' changed from '" + trgVal + "' to '" + srcVal + "'"); } trgProps.remove(name); } for (String name : trgProps) { trgObj.removeField(name); hasChanged = true; LOGGER.trace("for doc '" + trgObj.getDocumentReference() + "' field '" + name + "' set to null"); } return hasChanged; } }