Search in sources :

Example 1 with MergeResult

use of com.xpn.xwiki.doc.merge.MergeResult in project xwiki-platform by xwiki.

the class DocumentMergeImporter method merge.

private XarEntryMergeResult merge(String comment, XWikiDocument currentDocument, XWikiDocument previousDocument, XWikiDocument nextDocument, PackageConfiguration configuration) throws Exception {
    XWikiContext xcontext = this.xcontextProvider.get();
    // 3 ways merge
    XWikiDocument mergedDocument = currentDocument.clone();
    MergeConfiguration mergeConfiguration = new MergeConfiguration();
    mergeConfiguration.setProvidedVersionsModifiables(true);
    MergeResult documentMergeResult;
    try {
        documentMergeResult = mergedDocument.merge(previousDocument, nextDocument, mergeConfiguration, xcontext);
    } catch (Exception e) {
        // Unexpected error, lets behave as if there was a conflict
        documentMergeResult = new MergeResult();
        documentMergeResult.getLog().error("Unexpected exception thrown. Usually means there is a bug in the merge.", e);
        documentMergeResult.setModified(true);
    }
    documentMergeResult.getLog().log(this.logger);
    XWikiDocument documentToSave;
    if (documentMergeResult.isModified() || !documentMergeResult.getLog().getLogsFrom(LogLevel.ERROR).isEmpty()) {
        documentToSave = askDocumentToSave(currentDocument, previousDocument, nextDocument, mergedDocument, configuration, documentMergeResult);
        if (documentToSave != currentDocument) {
            saveDocument(documentToSave, comment, false, configuration);
        }
    }
    return new XarEntryMergeResult(new XarEntry(new LocalDocumentReference(mergedDocument.getDocumentReferenceWithLocale())), documentMergeResult);
}
Also used : LocalDocumentReference(org.xwiki.model.reference.LocalDocumentReference) XWikiDocument(com.xpn.xwiki.doc.XWikiDocument) XarEntry(org.xwiki.xar.XarEntry) MergeResult(com.xpn.xwiki.doc.merge.MergeResult) XWikiContext(com.xpn.xwiki.XWikiContext) MergeConfiguration(com.xpn.xwiki.doc.merge.MergeConfiguration)

Example 2 with MergeResult

use of com.xpn.xwiki.doc.merge.MergeResult in project xwiki-platform by xwiki.

the class DefaultDocumentMergeImporterTest method setUp.

@Before
public void setUp() throws Exception {
    this.xwiki = mock(XWiki.class);
    when(this.xcontext.getWiki()).thenReturn(this.xwiki);
    // documents
    this.previousDocument = mock(XWikiDocument.class, "previous");
    when(this.previousDocument.isNew()).thenReturn(false);
    when(this.previousDocument.getDocumentReferenceWithLocale()).thenReturn(this.documentReference);
    this.currentDocument = mock(XWikiDocument.class, "current");
    when(this.currentDocument.isNew()).thenReturn(false);
    when(this.currentDocument.getDocumentReferenceWithLocale()).thenReturn(this.documentReference);
    when(this.xwiki.getDocument(same(this.documentReference), same(xcontext))).thenReturn(this.currentDocument);
    this.nextDocument = mock(XWikiDocument.class, "next");
    when(this.nextDocument.isNew()).thenReturn(false);
    when(this.nextDocument.getDocumentReferenceWithLocale()).thenReturn(this.documentReference);
    this.mergedDocument = mock(XWikiDocument.class, "merged");
    when(this.mergedDocument.isNew()).thenReturn(false);
    when(this.mergedDocument.getDocumentReferenceWithLocale()).thenReturn(this.documentReference);
    when(this.currentDocument.clone()).thenReturn(this.mergedDocument);
    // merge
    this.configuration = new PackageConfiguration();
    this.mergeResult = new MergeResult();
    when(this.mergedDocument.merge(same(this.previousDocument), same(this.nextDocument), any(MergeConfiguration.class), any(XWikiContext.class))).thenReturn(this.mergeResult);
    // job status
    this.jobStatus = mock(JobStatus.class);
    this.configuration.setJobStatus(this.jobStatus);
    // execution
    this.econtext = new ExecutionContext();
    this.execution = this.mocker.getInstance(Execution.class);
    when(this.execution.getContext()).thenReturn(this.econtext);
}
Also used : JobStatus(org.xwiki.job.event.status.JobStatus) XWikiDocument(com.xpn.xwiki.doc.XWikiDocument) ExecutionContext(org.xwiki.context.ExecutionContext) Execution(org.xwiki.context.Execution) MergeResult(com.xpn.xwiki.doc.merge.MergeResult) XWiki(com.xpn.xwiki.XWiki) XWikiContext(com.xpn.xwiki.XWikiContext) MergeConfiguration(com.xpn.xwiki.doc.merge.MergeConfiguration) PackageConfiguration(org.xwiki.extension.xar.internal.handler.packager.PackageConfiguration) Before(org.junit.Before)

Example 3 with MergeResult

use of com.xpn.xwiki.doc.merge.MergeResult in project xwiki-platform by xwiki.

the class XWikiDocument method merge.

/**
 * Apply a 3 ways merge on the current document based on provided previous and new version of the document.
 * <p>
 * All 3 documents are supposed to have the same document reference and language already since that's what makes
 * them uniques.
 *
 * @param previousDocument the previous version of the document
 * @param newDocument the next version of the document
 * @param configuration the configuration of the merge indicates how to deal with some conflicts use cases, etc.
 * @param context the XWiki context
 * @return a repport of what happen during the merge (errors, etc.)
 * @since 3.2M1
 */
public MergeResult merge(XWikiDocument previousDocument, XWikiDocument newDocument, MergeConfiguration configuration, XWikiContext context) {
    MergeResult mergeResult = new MergeResult();
    // Title
    setTitle(MergeUtils.mergeOject(previousDocument.getTitle(), newDocument.getTitle(), getTitle(), mergeResult));
    // Content
    setContent(MergeUtils.mergeLines(previousDocument.getContent(), newDocument.getContent(), getContent(), mergeResult));
    // Syntax
    setSyntax(MergeUtils.mergeOject(previousDocument.getSyntax(), newDocument.getSyntax(), getSyntax(), mergeResult));
    // Default locale
    setDefaultLocale(MergeUtils.mergeOject(previousDocument.getDefaultLocale(), newDocument.getDefaultLocale(), getDefaultLocale(), mergeResult));
    // Parent
    setParentReference(MergeUtils.mergeOject(previousDocument.getRelativeParentReference(), newDocument.getRelativeParentReference(), getRelativeParentReference(), mergeResult));
    // DefaultTemplate
    setDefaultTemplate(MergeUtils.mergeOject(previousDocument.getDefaultTemplate(), newDocument.getDefaultTemplate(), getDefaultTemplate(), mergeResult));
    // Hidden
    setHidden(MergeUtils.mergeOject(previousDocument.isHidden(), newDocument.isHidden(), isHidden(), mergeResult));
    // CustomClass
    setCustomClass(MergeUtils.mergeLines(previousDocument.getCustomClass(), newDocument.getCustomClass(), getCustomClass(), mergeResult));
    // ValidationScript
    setValidationScript(MergeUtils.mergeLines(previousDocument.getValidationScript(), newDocument.getValidationScript(), getValidationScript(), mergeResult));
    // Objects
    List<List<ObjectDiff>> objectsDiff = previousDocument.getObjectDiff(previousDocument, newDocument, context);
    if (!objectsDiff.isEmpty()) {
        // Apply diff on result
        for (List<ObjectDiff> objectClassDiff : objectsDiff) {
            for (ObjectDiff diff : objectClassDiff) {
                BaseObject objectResult = getXObject(diff.getXClassReference(), diff.getNumber());
                BaseObject previousObject = previousDocument.getXObject(diff.getXClassReference(), diff.getNumber());
                BaseObject newObject = newDocument.getXObject(diff.getXClassReference(), diff.getNumber());
                PropertyInterface propertyResult = objectResult != null ? objectResult.getField(diff.getPropName()) : null;
                PropertyInterface previousProperty = previousObject != null ? previousObject.getField(diff.getPropName()) : null;
                PropertyInterface newProperty = newObject != null ? newObject.getField(diff.getPropName()) : null;
                if (diff.getAction() == ObjectDiff.ACTION_OBJECTADDED) {
                    if (objectResult == null) {
                        setXObject(newObject.getNumber(), configuration.isProvidedVersionsModifiables() ? newObject : newObject.clone());
                        mergeResult.setModified(true);
                    } else {
                        // collision between DB and new: object to add but already exists in the DB
                        mergeResult.getLog().error("Collision found on object [{}]", objectResult.getReference());
                    }
                } else if (diff.getAction() == ObjectDiff.ACTION_OBJECTREMOVED) {
                    if (objectResult != null) {
                        if (objectResult.equals(previousObject)) {
                            removeXObject(objectResult);
                            mergeResult.setModified(true);
                        } else {
                            // collision between DB and new: object to remove but not the same as previous
                            // version
                            mergeResult.getLog().error("Collision found on object [{}]", objectResult.getReference());
                        }
                    } else {
                        // Already removed from DB, lets assume the user is prescient
                        mergeResult.getLog().warn("Object [{}] already removed", previousObject.getReference());
                    }
                } else if (previousObject != null && newObject != null) {
                    if (objectResult != null) {
                        if (diff.getAction() == ObjectDiff.ACTION_PROPERTYADDED) {
                            if (propertyResult == null) {
                                objectResult.safeput(diff.getPropName(), newProperty);
                                mergeResult.setModified(true);
                            } else {
                                // collision between DB and new: property to add but already exists in the DB
                                mergeResult.getLog().error("Collision found on object property [{}]", propertyResult.getReference());
                            }
                        } else if (diff.getAction() == ObjectDiff.ACTION_PROPERTYREMOVED) {
                            if (propertyResult != null) {
                                if (propertyResult.equals(previousProperty)) {
                                    objectResult.removeField(diff.getPropName());
                                    mergeResult.setModified(true);
                                } else {
                                    // collision between DB and new: supposed to be removed but the DB version is
                                    // not the same as the previous version
                                    mergeResult.getLog().error("Collision found on object property [{}]", propertyResult.getReference());
                                }
                            } else {
                                // Already removed from DB, lets assume the user is prescient
                                mergeResult.getLog().warn("Object property [{}] already removed", previousProperty.getReference());
                            }
                        } else if (diff.getAction() == ObjectDiff.ACTION_PROPERTYCHANGED) {
                            if (propertyResult != null) {
                                if (propertyResult.equals(previousProperty)) {
                                    objectResult.safeput(diff.getPropName(), newProperty);
                                    mergeResult.setModified(true);
                                } else {
                                    // Try to apply a 3 ways merge on the property
                                    propertyResult.merge(previousProperty, newProperty, configuration, context, mergeResult);
                                }
                            } else {
                                // collision between DB and new: property to modify but does not exists in DB
                                // Lets assume it's a mistake to fix
                                mergeResult.getLog().warn("Object [{}] does not exists", newProperty.getReference());
                                objectResult.safeput(diff.getPropName(), newProperty);
                                mergeResult.setModified(true);
                            }
                        }
                    } else {
                        // Object explitely removed from the DB, lets assume we don't care about the changes
                        mergeResult.getLog().warn("Object [{}] already removed", previousObject.getReference());
                    }
                }
            }
        }
    }
    // Class
    BaseClass classResult = getXClass();
    BaseClass previousClass = previousDocument.getXClass();
    BaseClass newClass = newDocument.getXClass();
    classResult.merge(previousClass, newClass, configuration, context, mergeResult);
    // Attachments
    List<AttachmentDiff> attachmentsDiff = previousDocument.getAttachmentDiff(previousDocument, newDocument, context);
    if (!attachmentsDiff.isEmpty()) {
        // Apply deleted attachment diff on result (new attachment has already been saved)
        for (AttachmentDiff diff : attachmentsDiff) {
            XWikiAttachment previousAttachment = diff.getOrigAttachment();
            XWikiAttachment nextAttachment = diff.getNewAttachment();
            XWikiAttachment attachment = getAttachment(diff.getFileName());
            switch(diff.getType()) {
                case DELETE:
                    if (attachment != null) {
                        try {
                            if (attachment.equalsData(previousAttachment, context)) {
                                removeAttachment(attachment);
                                mergeResult.setModified(true);
                            } else {
                                // collision between DB and new: attachment modified by user
                                mergeResult.getLog().error("Collision found on attachment [{}]", attachment.getReference());
                            }
                        } catch (XWikiException e) {
                            mergeResult.getLog().error("Failed to compare attachments with reference [{}]", attachment.getReference());
                        }
                    } else {
                        // Already removed from DB, lets assume the user is prescient
                        mergeResult.getLog().warn("Attachment [{}] already removed", previousAttachment.getReference());
                    }
                    break;
                case INSERT:
                    if (attachment != null) {
                        try {
                            if (!attachment.equalsData(nextAttachment, context)) {
                                // collision between DB and new: attachment to add but a different one already
                                // exists in the DB
                                mergeResult.getLog().error("Collision found on attachment [{}]", attachment.getReference());
                            } else {
                                // Already added to the DB, lets assume the user is prescient
                                mergeResult.getLog().warn("Attachment [{}] already added", previousAttachment.getReference());
                            }
                        } catch (XWikiException e) {
                            mergeResult.getLog().error("Failed to compare attachments with reference [{}]", attachment.getReference());
                        }
                    } else {
                        addAttachment(configuration.isProvidedVersionsModifiables() ? nextAttachment : (XWikiAttachment) nextAttachment.clone());
                        mergeResult.setModified(true);
                    }
                    break;
                case CHANGE:
                    if (attachment != null) {
                        attachment.merge(previousAttachment, nextAttachment, configuration, context, mergeResult);
                    } else {
                        // collision between DB and new: attachment modified but does not exist in the DB
                        mergeResult.getLog().error("Collision found on attachment [{}]", previousAttachment.getReference());
                    }
                    break;
                default:
                    break;
            }
        }
    }
    return mergeResult;
}
Also used : ObjectDiff(com.xpn.xwiki.objects.ObjectDiff) PropertyInterface(com.xpn.xwiki.objects.PropertyInterface) MergeResult(com.xpn.xwiki.doc.merge.MergeResult) BaseClass(com.xpn.xwiki.objects.classes.BaseClass) XWikiAttachmentList(com.xpn.xwiki.internal.doc.XWikiAttachmentList) ArrayList(java.util.ArrayList) List(java.util.List) XWikiException(com.xpn.xwiki.XWikiException) BaseObject(com.xpn.xwiki.objects.BaseObject)

Example 4 with MergeResult

use of com.xpn.xwiki.doc.merge.MergeResult in project xwiki-platform by xwiki.

the class XWikiDocumentMergeTest method testMergeAttachmentNew.

@Test
public void testMergeAttachmentNew() throws Exception {
    XWikiAttachment attachment = new XWikiAttachment();
    attachment.setContent(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });
    attachment.setLongSize(10);
    attachment.setFilename("file");
    this.nextDocument.addAttachment(attachment);
    MergeResult result = merge();
    Assert.assertTrue(result.isModified());
    XWikiAttachment newAttachment = this.currentDocument.getAttachment("file");
    Assert.assertNotNull(newAttachment);
    Assert.assertEquals(10, newAttachment.getLongSize());
    Assert.assertArrayEquals(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, newAttachment.getContent(null));
}
Also used : MergeResult(com.xpn.xwiki.doc.merge.MergeResult) Test(org.junit.Test)

Example 5 with MergeResult

use of com.xpn.xwiki.doc.merge.MergeResult in project xwiki-platform by xwiki.

the class XWikiDocumentMergeTest method testMergeAttachmentDeleted.

@Test
public void testMergeAttachmentDeleted() throws Exception {
    XWikiAttachment attachment = new XWikiAttachment();
    attachment.setContent(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });
    attachment.setLongSize(10);
    attachment.setFilename("file");
    this.currentDocument.addAttachment(attachment);
    this.previousDocument.addAttachment((XWikiAttachment) attachment.clone());
    MergeResult result = merge();
    Assert.assertTrue(result.isModified());
    XWikiAttachment newAttachment = this.currentDocument.getAttachment("file");
    Assert.assertNull(newAttachment);
}
Also used : MergeResult(com.xpn.xwiki.doc.merge.MergeResult) Test(org.junit.Test)

Aggregations

MergeResult (com.xpn.xwiki.doc.merge.MergeResult)21 Test (org.junit.Test)17 ArrayList (java.util.ArrayList)4 MergeConfiguration (com.xpn.xwiki.doc.merge.MergeConfiguration)3 XWikiContext (com.xpn.xwiki.XWikiContext)2 XWikiDocument (com.xpn.xwiki.doc.XWikiDocument)2 LogEvent (org.xwiki.logging.event.LogEvent)2 XWiki (com.xpn.xwiki.XWiki)1 XWikiException (com.xpn.xwiki.XWikiException)1 MergeException (com.xpn.xwiki.doc.merge.MergeException)1 XWikiAttachmentList (com.xpn.xwiki.internal.doc.XWikiAttachmentList)1 BaseObject (com.xpn.xwiki.objects.BaseObject)1 ObjectDiff (com.xpn.xwiki.objects.ObjectDiff)1 PropertyInterface (com.xpn.xwiki.objects.PropertyInterface)1 BaseClass (com.xpn.xwiki.objects.classes.BaseClass)1 List (java.util.List)1 Before (org.junit.Before)1 Execution (org.xwiki.context.Execution)1 ExecutionContext (org.xwiki.context.ExecutionContext)1 PackageConfiguration (org.xwiki.extension.xar.internal.handler.packager.PackageConfiguration)1