/* * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.xwiki.extension.xar.internal.handler.packager; import java.util.Date; import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; import org.slf4j.Logger; import org.xwiki.component.annotation.Component; import org.xwiki.context.Execution; import org.xwiki.extension.xar.question.ConflictQuestion; import org.xwiki.extension.xar.question.ConflictQuestion.GlobalAction; import org.xwiki.logging.LogLevel; import org.xwiki.model.reference.DocumentReference; import org.xwiki.model.reference.LocalDocumentReference; import org.xwiki.xar.XarEntry; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.doc.MandatoryDocumentInitializer; import com.xpn.xwiki.doc.MandatoryDocumentInitializerManager; import com.xpn.xwiki.doc.XWikiAttachment; import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.doc.merge.MergeConfiguration; import com.xpn.xwiki.doc.merge.MergeResult; /** * Take care of properly merging and saving a document. * * @version $Id: dfa93a959b804db0d96f99bcc548ef765ef4ae3e $ */ @Component(roles = DocumentMergeImporter.class) @Singleton public class DocumentMergeImporter { @Inject private Provider<XWikiContext> xcontextProvider; @Inject private MandatoryDocumentInitializerManager initializerManager; @Inject private Execution execution; @Inject private Logger logger; /** * @param comment the comment to use when/if saving the document * @param previousDocument the previous version of the document * @param currentDocument the current version of the document * @param nextDocument the new version of the document * @param configuration various setup for the import * @return the result of the merge * @throws Exception when failed to save the document */ public XarEntryMergeResult saveDocument(String comment, XWikiDocument previousDocument, XWikiDocument currentDocument, XWikiDocument nextDocument, PackageConfiguration configuration) throws Exception { XarEntryMergeResult mergeResult = null; // Merge and save if (currentDocument != null && !currentDocument.isNew()) { if (previousDocument != null) { // 3 ways merge mergeResult = merge(comment, currentDocument, previousDocument, nextDocument, configuration); } else { // Check if a mandatory document initializer exists for the current document XWikiDocument mandatoryDocument = getMandatoryDocument(nextDocument.getDocumentReference()); if (mandatoryDocument != null) { // 3 ways merge mergeResult = merge(comment, currentDocument, mandatoryDocument, nextDocument, configuration); } else { // Already existing document in database but without previous version if (!currentDocument.equalsData(nextDocument)) { XWikiDocument documentToSave = askDocumentToSave(currentDocument, null, nextDocument, null, configuration, null); if (documentToSave != currentDocument) { saveDocument(documentToSave, comment, false, configuration); } } } } } else if (previousDocument != null) { // Document have been deleted in the database XWikiDocument documentToSave = askDocumentToSave(null, previousDocument, nextDocument, null, configuration, null); if (documentToSave != null) { saveDocument(documentToSave, comment, true, configuration); } } else { // Simple install (the document does not exist in previous version or in the database) saveDocument(nextDocument, comment, true, configuration); } return mergeResult; } 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); } private XWikiDocument getMandatoryDocument(DocumentReference documentReference) { MandatoryDocumentInitializer initializer = this.initializerManager.getMandatoryDocumentInitializer(documentReference); XWikiDocument mandatoryDocument; if (initializer != null) { // Generate clean mandatory document mandatoryDocument = new XWikiDocument(documentReference); if (!initializer.updateDocument(mandatoryDocument)) { mandatoryDocument = null; } } else { mandatoryDocument = null; } return mandatoryDocument; } private GlobalAction getMergeConflictAnswer(ConflictQuestion.ConflictType type, PackageConfiguration configuration) { GlobalAction action = (GlobalAction) this.execution.getContext().getProperty(ConflictQuestion.toKey(type)); if (action == null && configuration != null) { action = configuration.getConflictAction(type); } return action; } private void setMergeConflictAnswer(ConflictQuestion.ConflictType type, GlobalAction action) { this.execution.getContext().setProperty(ConflictQuestion.toKey(type), action); } private XWikiDocument askDocumentToSave(XWikiDocument currentDocument, XWikiDocument previousDocument, XWikiDocument nextDocument, XWikiDocument mergedDocument, PackageConfiguration configuration, MergeResult documentMergeResult) { // Indicate future author to whoever is going to answer the question if (currentDocument != null) { nextDocument.setCreatorReference(currentDocument.getCreatorReference()); } if (mergedDocument != null) { mergedDocument.setCreatorReference(currentDocument.getCreatorReference()); } DocumentReference userReference = configuration != null ? configuration.getUserReference() : null; if (userReference != null) { nextDocument.setAuthorReference(userReference); nextDocument.setContentAuthorReference(userReference); for (XWikiAttachment attachment : nextDocument.getAttachmentList()) { attachment.setAuthorReference(nextDocument.getAuthorReference()); } if (mergedDocument != null) { mergedDocument.setAuthorReference(userReference); mergedDocument.setContentAuthorReference(userReference); for (XWikiAttachment attachment : mergedDocument.getAttachmentList()) { if (attachment.isContentDirty()) { attachment.setAuthorReference(mergedDocument.getAuthorReference()); } } } } // Calculate the conflict type ConflictQuestion.ConflictType type; if (previousDocument == null) { type = ConflictQuestion.ConflictType.CURRENT_EXIST; } else if (currentDocument == null) { type = ConflictQuestion.ConflictType.CURRENT_DELETED; } else if (documentMergeResult != null) { if (!documentMergeResult.getLog().getLogs(LogLevel.ERROR).isEmpty()) { type = ConflictQuestion.ConflictType.MERGE_FAILURE; } else { type = ConflictQuestion.ConflictType.MERGE_SUCCESS; } } else { type = null; } // Create a question ConflictQuestion question = new ConflictQuestion(currentDocument, previousDocument, nextDocument, mergedDocument, type); // Find the answer GlobalAction contextAction = getMergeConflictAnswer(question.getType(), configuration); if (contextAction != null && contextAction != GlobalAction.ASK) { question.setGlobalAction(contextAction); } else if (configuration != null && configuration.getJobStatus() != null && configuration.isInteractive()) { try { // Ask what to do configuration.getJobStatus().ask(question); if (question.isAlways()) { setMergeConflictAnswer(question.getType(), question.getGlobalAction()); } } catch (InterruptedException e) { // TODO: log something ? } } // Find the XWikiDocument to save XWikiDocument documentToSave; switch (question.getGlobalAction()) { case CURRENT: documentToSave = currentDocument; break; case NEXT: documentToSave = nextDocument; break; case PREVIOUS: documentToSave = previousDocument; break; case CUSTOM: documentToSave = question.getCustomDocument() != null ? question.getCustomDocument() : mergedDocument; break; default: documentToSave = mergedDocument; break; } return documentToSave; } private void saveDocument(XWikiDocument document, String comment, boolean setCreator, PackageConfiguration configuration) throws Exception { XWikiContext xcontext = this.xcontextProvider.get(); XWikiDocument currentDocument = xcontext.getWiki().getDocument(document.getDocumentReferenceWithLocale(), xcontext); if (!currentDocument.isNew()) { if (document != currentDocument) { if (document.isNew()) { currentDocument.loadAttachmentsContent(xcontext); currentDocument.apply(document); } else { currentDocument = document; } } } else { currentDocument = document; } // Set document authors DocumentReference configuredUser = configuration.getUserReference(); if (configuredUser != null) { if (setCreator) { currentDocument.setCreatorReference(configuredUser); } currentDocument.setAuthorReference(configuredUser); currentDocument.setContentAuthorReference(configuredUser); // Set attachments authors for (XWikiAttachment attachment : currentDocument.getAttachmentList()) { if (attachment.isContentDirty()) { attachment.setAuthorReference(currentDocument.getAuthorReference()); } } } else { if (document != currentDocument) { if (setCreator) { currentDocument.setCreatorReference(document.getCreatorReference()); } currentDocument.setAuthorReference(document.getAuthorReference()); currentDocument.setContentAuthorReference(document.getContentAuthorReference()); // Set attachments authors for (XWikiAttachment attachment : document.getAttachmentList()) { if (attachment.isContentDirty()) { currentDocument.getAttachment(attachment.getFilename()) .setAuthorReference(attachment.getAuthorReference()); } } } // Make sure to keep the content author we want currentDocument.setContentDirty(false); currentDocument.setContentUpdateDate(new Date()); } saveDocumentSetContextUser(currentDocument, comment); } private void saveDocumentSetContextUser(XWikiDocument document, String comment) throws Exception { XWikiContext xcontext = this.xcontextProvider.get(); DocumentReference userReference = xcontext.getUserReference(); try { // Make sure to have context user corresponding to document author for badly designed listeners expecting // the document to actually be saved by context user xcontext.setUserReference(document.getAuthorReference()); xcontext.getWiki().saveDocument(document, comment, false, xcontext); } finally { xcontext.setUserReference(userReference); } } }