/* * 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.job; import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.HashSet; import java.util.Set; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; import org.xwiki.component.annotation.Component; import org.xwiki.extension.ExtensionDependency; import org.xwiki.extension.ExtensionId; import org.xwiki.extension.InstalledExtension; import org.xwiki.extension.job.InstallRequest; import org.xwiki.extension.job.internal.AbstractExtensionJob; import org.xwiki.extension.xar.internal.handler.UnsupportedNamespaceException; import org.xwiki.extension.xar.internal.handler.XarExtensionHandler; import org.xwiki.extension.xar.internal.handler.XarHandlerUtils; import org.xwiki.extension.xar.internal.handler.packager.Packager; import org.xwiki.extension.xar.internal.job.diff.DocumentUnifiedDiffBuilder; import org.xwiki.extension.xar.job.diff.DiffXarJobStatus; import org.xwiki.extension.xar.job.diff.DocumentUnifiedDiff; import org.xwiki.extension.xar.job.diff.DocumentVersionReference; import org.xwiki.model.reference.LocalDocumentReference; import org.xwiki.model.reference.WikiReference; import org.xwiki.xar.XarEntry; import org.xwiki.xar.XarException; import org.xwiki.xar.XarFile; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.doc.XWikiDocument; /** * Computes the differences between the documents provided by a XAR extension and the documents from the database. * * @version $Id: 7c0c9dee8d35341733a2e1550c8b57768bf6c1cd $ * @since 7.0RC1 */ @Component @Named(DiffXarJob.JOB_TYPE) public class DiffXarJob extends AbstractExtensionJob<InstallRequest, DiffXarJobStatus> { /** * The id of the job. */ public static final String JOB_TYPE = "diffXar"; /** * Used to get the documents from the XAR. */ @Inject private Packager packager; /** * Used to get the documents from the database. */ @Inject private Provider<XWikiContext> xcontextProvider; /** * Used to compute the differences. */ @Inject private DocumentUnifiedDiffBuilder documentDiffBuilder; /** * The set of features that have been compared. We try to avoid comparing the same feature twice. We assume all the * features are compared on the same namespace. */ private Set<String> comparedFeatures = new HashSet<>(); @Override public String getType() { return JOB_TYPE; } @Override protected DiffXarJobStatus createNewStatus(InstallRequest request) { return new DiffXarJobStatus(request, this.observationManager, this.loggerManager); } @Override protected void runInternal() throws Exception { InstallRequest request = getRequest(); // There must be only one namespace specified because we compute the differences for only one wiki. if (!request.hasNamespaces() || request.getNamespaces().size() != 1) { return; } String namespace = request.getNamespaces().iterator().next(); Collection<ExtensionId> extensionIds = request.getExtensions(); this.progressManager.pushLevelProgress(extensionIds.size(), this); try { for (ExtensionId extensionId : extensionIds) { this.progressManager.startStep(this); InstalledExtension installedExtension = getInstalledExtension(extensionId, namespace); // Make sure the specified extension is installed on the specified namespace. if (installedExtension != null && installedExtension.isInstalled(namespace)) { diff(extensionId.getId(), namespace, new HashSet<>()); } this.progressManager.endStep(this); } } finally { this.progressManager.popLevelProgress(this); } } private InstalledExtension getInstalledExtension(ExtensionId extensionId, String namespace) { if (extensionId.getVersion() != null) { return this.installedExtensionRepository.getInstalledExtension(extensionId); } else { return this.installedExtensionRepository.getInstalledExtension(extensionId.getId(), namespace); } } private void diff(String feature, String namespace, Set<LocalDocumentReference> alreadydone) { if (this.comparedFeatures.contains(feature)) { // We already looked at this feature. return; } this.comparedFeatures.add(feature); InstalledExtension installedExtension = this.installedExtensionRepository.getInstalledExtension(feature, namespace); if (installedExtension != null) { diff(installedExtension, namespace, alreadydone); Collection<? extends ExtensionDependency> dependencies = installedExtension.getDependencies(); this.progressManager.pushLevelProgress(dependencies.size(), this); try { for (ExtensionDependency dependency : dependencies) { this.progressManager.startStep(this); diff(dependency.getId(), namespace, new HashSet<>(alreadydone)); } } finally { this.progressManager.popLevelProgress(this); } } } private void diff(InstalledExtension installedExtension, String namespace, Set<LocalDocumentReference> alreadydone) { Collection<ExtensionId> excludedExtensions = getRequest().getExcludedExtensions(); if (XarExtensionHandler.TYPE.equals(installedExtension.getType()) && (excludedExtensions == null || !excludedExtensions.contains(installedExtension.getId()))) { if (getRequest().isVerbose()) { this.logger.info("Computing differences for [{}] on namespace [{}]", installedExtension.getId(), namespace); } try { WikiReference wikiReference = new WikiReference(XarHandlerUtils.getWikiFromNamespace(namespace)); diff(new XarFile(new File(installedExtension.getFile().getAbsolutePath())), wikiReference, installedExtension.getId(), alreadydone); } catch (UnsupportedNamespaceException e) { this.logger.error("Failed to extract the wiki id from the namespace [{}].", namespace, e); } catch (IOException e) { this.logger.error("Failed to read the XAR file of the extension [{}].", installedExtension.getId(), e); } catch (XarException e) { this.logger.error("Failed to parse the XAR file of the extension [{}].", installedExtension.getId(), e); } } } private void diff(XarFile xarFile, WikiReference wikiReference, ExtensionId extensionId, Set<LocalDocumentReference> alreadydone) { Collection<XarEntry> xarEntries = xarFile.getEntries(); this.progressManager.pushLevelProgress(xarEntries.size(), this); try { for (XarEntry xarEntry : xarEntries) { this.progressManager.startStep(this); if (!alreadydone.contains(xarEntry)) { try { diff(this.packager.getXWikiDocument(xarFile.getInputStream(xarEntry), wikiReference), extensionId); } catch (Exception e) { // Skip this document and continue. this.logger.error("Failed to parse document [{}] from XAR.", xarEntry.getDocumentName(), e); } alreadydone.add(xarEntry); } } } finally { try { xarFile.close(); } catch (IOException e) { // Ignore. } this.progressManager.popLevelProgress(this); } } private void diff(XWikiDocument document, ExtensionId extensionId) { if (getRequest().isVerbose()) { this.logger.info("Computing differences for document [{}]", document.getDocumentReferenceWithLocale()); } // Use the extension id as the document version. XWikiDocument previousDocument = document.duplicate(new DocumentVersionReference(document.getDocumentReference(), extensionId)); XWikiContext xcontext = this.xcontextProvider.get(); try { XWikiDocument nextDocument = xcontext.getWiki().getDocument(document.getDocumentReferenceWithLocale(), xcontext); if (nextDocument.isNew()) { nextDocument = null; } maybeAddDocumentDiff(this.documentDiffBuilder.diff(previousDocument, nextDocument)); } catch (XWikiException e) { this.logger.error("Failed to get document [{}] from the database.", document.getDocumentReference(), e); } } private void maybeAddDocumentDiff(DocumentUnifiedDiff documentDiff) { int differencesCount = documentDiff.size() + documentDiff.getAttachmentDiffs().size() + documentDiff.getObjectDiffs().size() + documentDiff.getClassPropertyDiffs().size(); if (getRequest().isVerbose()) { if (documentDiff.getNextReference() == null) { this.logger.info("The document [{}] has been deleted", documentDiff.getPreviousReference()); } else if (documentDiff.getPreviousReference() == null) { this.logger.info("The document [{}] has been added", documentDiff.getNextReference()); } else if (differencesCount > 0) { this.logger.info("The document [{}] has [{}] changes", documentDiff.getPreviousReference(), differencesCount); } else { this.logger.info("The document [{}] has no changes", documentDiff.getPreviousReference()); } } if (differencesCount > 0) { getStatus().getDocumentDiffs().add(documentDiff); } } }