/*
* 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.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import org.slf4j.Logger;
import org.xwiki.component.annotation.Component;
import org.xwiki.component.manager.ComponentLookupException;
import org.xwiki.extension.ExtensionId;
import org.xwiki.extension.repository.InstalledExtensionRepository;
import org.xwiki.extension.xar.XarExtensionExtension;
import org.xwiki.extension.xar.internal.handler.XarExtensionHandler;
import org.xwiki.extension.xar.internal.handler.XarExtensionPlan;
import org.xwiki.extension.xar.internal.repository.XarInstalledExtension;
import org.xwiki.extension.xar.internal.repository.XarInstalledExtensionRepository;
import org.xwiki.extension.xar.job.diff.DocumentVersionReference;
import org.xwiki.filter.FilterException;
import org.xwiki.filter.input.DefaultInputStreamInputSource;
import org.xwiki.filter.instance.output.DocumentInstanceOutputProperties;
import org.xwiki.filter.xar.input.XARInputProperties;
import org.xwiki.logging.marker.BeginTranslationMarker;
import org.xwiki.logging.marker.EndTranslationMarker;
import org.xwiki.logging.marker.TranslationMarker;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.DocumentReferenceResolver;
import org.xwiki.model.reference.EntityReference;
import org.xwiki.model.reference.LocalDocumentReference;
import org.xwiki.model.reference.WikiReference;
import org.xwiki.observation.ObservationManager;
import org.xwiki.wiki.descriptor.WikiDescriptorManager;
import org.xwiki.wiki.manager.WikiManagerException;
import org.xwiki.xar.XarEntry;
import org.xwiki.xar.XarException;
import org.xwiki.xar.XarFile;
import org.xwiki.xar.internal.model.XarModel;
import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.MandatoryDocumentInitializerManager;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.internal.event.XARImportedEvent;
import com.xpn.xwiki.internal.event.XARImportingEvent;
import com.xpn.xwiki.internal.filter.XWikiDocumentFilterUtils;
/**
* Default implementation of {@link Packager}.
*
* @version $Id: d39d4ac9b979683b8a6d0e4728bf3b2ee21f67fb $
* @since 4.0M1
*/
@Component(roles = Packager.class)
@Singleton
public class Packager
{
private static final BeginTranslationMarker LOG_INSTALLDOCUMENT_BEGIN =
new BeginTranslationMarker("extension.xar.log.install.document.begin");
private static final EndTranslationMarker LOG_INSTALLDOCUMENT_SUCCESS_END =
new EndTranslationMarker("extension.xar.log.install.document.success.end");
private static final EndTranslationMarker LOG_INSTALLDOCUMENT_FAILURE_END =
new EndTranslationMarker("extension.xar.log.install.document.failure.end");
private static final TranslationMarker LOG_DELETEDDOCUMENT =
new TranslationMarker("extension.xar.log.delete.document");
private static final TranslationMarker LOG_DELETEDDOCUMENT_FAILURE =
new TranslationMarker("extension.xar.log.delete.document.failure");
@Inject
@Named("explicit")
private DocumentReferenceResolver<EntityReference> resolver;
/**
* The logger to log.
*/
@Inject
private Logger logger;
@Inject
private ObservationManager observation;
@Inject
private Provider<XWikiContext> xcontextProvider;
@Inject
private DocumentMergeImporter importer;
@Inject
private MandatoryDocumentInitializerManager initializerManager;
@Inject
private XWikiDocumentFilterUtils documentImporter;
@Inject
private WikiDescriptorManager wikiDescriptors;
@Inject
@Named(XarExtensionHandler.TYPE)
private InstalledExtensionRepository installedXARs;
private XarInstalledExtensionRepository getXarInstalledExtensionRepository()
{
return (XarInstalledExtensionRepository) this.installedXARs;
}
public void importXAR(String comment, File xarFile, PackageConfiguration configuration)
throws IOException, XWikiException, ComponentLookupException, FilterException, WikiManagerException
{
if (configuration.getWiki() == null) {
Collection<String> wikis = this.wikiDescriptors.getAllIds();
for (String subwiki : wikis) {
importXARToWiki(comment, xarFile, new WikiReference(subwiki), configuration);
}
} else {
importXARToWiki(comment, xarFile, new WikiReference(configuration.getWiki()), configuration);
}
}
private XarMergeResult importXARToWiki(String comment, File xarFile, WikiReference wikiReference,
PackageConfiguration configuration)
throws IOException, ComponentLookupException, XWikiException, FilterException
{
FileInputStream fis = new FileInputStream(xarFile);
try {
return importXARToWiki(comment, fis, wikiReference, configuration);
} finally {
fis.close();
}
}
private XarMergeResult importXARToWiki(String comment, InputStream xarInputStream, WikiReference wikiReference,
PackageConfiguration configuration)
throws IOException, ComponentLookupException, XWikiException, FilterException
{
XarMergeResult mergeResult = new XarMergeResult();
ZipArchiveInputStream zis = new ZipArchiveInputStream(xarInputStream);
XWikiContext xcontext = this.xcontextProvider.get();
String currentWiki = xcontext.getWikiId();
try {
xcontext.setWikiId(wikiReference.getName());
this.observation.notify(new XARImportingEvent(), null, xcontext);
for (ArchiveEntry entry = zis.getNextEntry(); entry != null; entry = zis.getNextEntry()) {
if (!entry.isDirectory()) {
// Only import what should be imported
if (!entry.getName().equals(XarModel.PATH_PACKAGE) && (configuration.getEntriesToImport() == null
|| configuration.getEntriesToImport().contains(entry.getName()))) {
XarEntryMergeResult entityMergeResult =
importDocumentToWiki(comment, wikiReference, zis, configuration);
if (entityMergeResult != null) {
mergeResult.addMergeResult(entityMergeResult);
}
}
}
}
} finally {
this.observation.notify(new XARImportedEvent(), null, xcontext);
xcontext.setWikiId(currentWiki);
}
return mergeResult;
}
private XarEntryMergeResult importDocumentToWiki(String comment, WikiReference wikiReference,
InputStream inputStream, PackageConfiguration configuration)
throws XWikiException, FilterException, ComponentLookupException, IOException
{
XWikiContext xcontext = this.xcontextProvider.get();
XWikiDocument nextDocument;
try {
nextDocument = getXWikiDocument(inputStream, wikiReference);
} catch (Exception e) {
this.logger.error("Failed to parse document", e);
return null;
}
DocumentReference reference = nextDocument.getDocumentReferenceWithLocale();
XWikiDocument currentDocument = xcontext.getWiki().getDocument(reference, xcontext);
currentDocument.loadAttachmentsContent(xcontext);
XWikiDocument previousDocument;
XarExtensionPlan xarExtensionPlan = configuration.getXarExtensionPlan();
if (xarExtensionPlan != null) {
previousDocument = xarExtensionPlan.getPreviousXWikiDocument(reference, this);
} else {
previousDocument = null;
}
if (configuration.isVerbose()) {
this.logger.info(LOG_INSTALLDOCUMENT_BEGIN, "Installing document [{}]",
nextDocument.getDocumentReferenceWithLocale());
}
try {
XarEntryMergeResult entityMergeResult =
this.importer.saveDocument(comment, previousDocument, currentDocument, nextDocument, configuration);
if (configuration.isVerbose()) {
this.logger.info(LOG_INSTALLDOCUMENT_SUCCESS_END, "Done installing document [{}]",
nextDocument.getDocumentReferenceWithLocale());
}
return entityMergeResult;
} catch (Exception e) {
if (configuration.isVerbose()) {
this.logger.error(LOG_INSTALLDOCUMENT_FAILURE_END, "Failed to install document [{}]",
nextDocument.getDocumentReferenceWithLocale(), e);
}
}
return null;
}
public void unimportPages(Collection<XarEntry> pages, PackageConfiguration configuration)
throws WikiManagerException
{
if (configuration.getWiki() == null) {
Collection<String> wikis = this.wikiDescriptors.getAllIds();
for (String subwiki : wikis) {
unimportPagesFromWiki(pages, subwiki, configuration);
}
} else {
unimportPagesFromWiki(pages, configuration.getWiki(), configuration);
}
}
private void unimportPagesFromWiki(Collection<XarEntry> entries, String wiki, PackageConfiguration configuration)
{
WikiReference wikiReference = new WikiReference(wiki);
for (XarEntry xarEntry : entries) {
// Only delete what should be deleted.
if (configuration.getEntriesToImport() == null
|| configuration.getEntriesToImport().contains(xarEntry.getEntryName())) {
DocumentReference documentReference =
new DocumentReference(this.resolver.resolve(xarEntry, wikiReference), xarEntry.getLocale());
if (!configuration.isSkipMandatorytDocuments() || !isMandatoryDocument(documentReference)) {
deleteDocument(documentReference, configuration);
}
}
}
}
public void deleteDocument(DocumentReference documentReference, PackageConfiguration configuration)
{
XWikiContext xcontext = this.xcontextProvider.get();
try {
// Make sure to have an expected context as much as possible
if (configuration.getUserReference() != null) {
xcontext.setUserReference(configuration.getUserReference());
}
XWikiDocument document = xcontext.getWiki().getDocument(documentReference, xcontext);
if (!document.isNew()) {
xcontext.getWiki().deleteDocument(document, xcontext);
if (configuration.isVerbose()) {
this.logger.info(LOG_DELETEDDOCUMENT, "Deleted document [{}]",
document.getDocumentReferenceWithLocale());
}
}
} catch (XWikiException e) {
this.logger.error(LOG_DELETEDDOCUMENT_FAILURE, "Failed to delete document [{}]", documentReference, e);
}
}
private boolean isMandatoryDocument(DocumentReference documentReference)
{
return this.initializerManager.getMandatoryDocumentInitializer(documentReference) != null;
}
public XWikiDocument getXWikiDocument(WikiReference wikiReference, LocalDocumentReference documentReference,
XarFile xarFile) throws FilterException, IOException, ComponentLookupException
{
XarEntry realEntry = xarFile.getEntry(documentReference);
if (realEntry != null) {
InputStream stream = xarFile.getInputStream(realEntry);
try {
return getXWikiDocument(stream, wikiReference);
} finally {
stream.close();
}
}
return null;
}
private DocumentReference cleanDocumentReference(DocumentReference reference)
{
// Remove the version if any since it does not make sense in a XAR
DocumentReference documentReference = reference;
if (reference instanceof DocumentVersionReference) {
documentReference = ((DocumentVersionReference) reference).removeVersion();
}
return documentReference;
}
public XWikiDocument getXWikiDocument(DocumentReference reference)
throws FilterException, IOException, ComponentLookupException, XarException
{
if (reference != null) {
Collection<XarInstalledExtension> extensions =
getXarInstalledExtensionRepository().getXarInstalledExtensions(reference);
if (!extensions.isEmpty()) {
return getXWikiDocument(reference, extensions.iterator().next());
}
}
return null;
}
public XWikiDocument getXWikiDocument(DocumentReference reference, ExtensionId extensionId)
throws FilterException, IOException, ComponentLookupException, XarException
{
if (reference != null) {
if (extensionId != null) {
return getXWikiDocument(reference,
(XarInstalledExtension) this.installedXARs.getInstalledExtension(extensionId));
}
return getXWikiDocument(reference);
}
return null;
}
public XWikiDocument getXWikiDocument(DocumentReference reference, XarInstalledExtension extension)
throws FilterException, IOException, ComponentLookupException, XarException
{
if (reference != null) {
// Remove the version if any since it does not make sense in a XAR
DocumentReference documentReference = cleanDocumentReference(reference);
return getXWikiDocument(documentReference.getWikiReference(), documentReference.getLocalDocumentReference(),
extension);
}
return null;
}
public XWikiDocument getXWikiDocument(WikiReference wikiReference, LocalDocumentReference documentReference,
XarInstalledExtension extension) throws FilterException, IOException, ComponentLookupException, XarException
{
try (
XarFile xarFile = new XarFile(new File(extension.getFile().getAbsolutePath()), extension.getXarPackage())) {
return getXWikiDocument(wikiReference, documentReference, xarFile);
}
}
public XWikiDocument getXWikiDocument(InputStream source, WikiReference wikiReference)
throws FilterException, IOException, ComponentLookupException
{
// Output
DocumentInstanceOutputProperties documentProperties = new DocumentInstanceOutputProperties();
documentProperties.setDefaultReference(wikiReference);
documentProperties.setVersionPreserved(false);
// Input
XARInputProperties xarProperties = new XARInputProperties();
xarProperties.setWithHistory(false);
return this.documentImporter.importDocument(new DefaultInputStreamInputSource(source), xarProperties,
documentProperties);
}
/**
* @since 9.3RC1
*/
public void reset(DocumentReference reference, DocumentReference authorReference) throws FilterException,
IOException, ComponentLookupException, XarException, XWikiException, XarExtensionExtension
{
Collection<XarInstalledExtension> installedExtensions =
getXarInstalledExtensionRepository().getXarInstalledExtensions(reference);
if (!installedExtensions.isEmpty()) {
XarInstalledExtension extension = installedExtensions.iterator().next();
// Remove the version if any since it does not make sense in a XAR
DocumentReference documentReference = cleanDocumentReference(reference);
XWikiDocument document = getXWikiDocument(documentReference, extension);
if (document != null) {
XWikiContext xcontext = this.xcontextProvider.get();
// Get database document
XWikiDocument databaseDocument = xcontext.getWiki().getDocument(documentReference, xcontext);
// Override data of database document with extension document
databaseDocument.apply(document, true);
// Make sure new version will have the right author
databaseDocument.setAuthorReference(authorReference);
databaseDocument.setContentAuthorReference(authorReference);
// Force generating new version
databaseDocument.setMetaDataDirty(true);
databaseDocument.setContentDirty(true);
// Save
xcontext.getWiki().saveDocument(databaseDocument, "Reset document from extension [" + extension + "]",
xcontext);
} else {
throw new XarExtensionExtension("Can't find any document with reference [" + documentReference
+ "] in extension [" + extension.getId() + "]");
}
} else {
throw new XarExtensionExtension(
"Can't find any installed extension associated with the document reference [" + reference + "]");
}
}
public List<DocumentReference> getDocumentReferences(Collection<XarEntry> pages, PackageConfiguration configuration)
throws WikiManagerException
{
List<DocumentReference> documents = new ArrayList<>(pages.size());
if (configuration.getWiki() == null) {
Collection<String> wikis = this.wikiDescriptors.getAllIds();
for (String subwiki : wikis) {
getDocumentReferencesFromWiki(documents, pages, subwiki, configuration);
}
} else {
getDocumentReferencesFromWiki(documents, pages, configuration.getWiki(), configuration);
}
return documents;
}
private void getDocumentReferencesFromWiki(List<DocumentReference> documents, Collection<XarEntry> pages,
String wiki, PackageConfiguration configuration)
{
WikiReference wikiReference = new WikiReference(wiki);
for (XarEntry xarEntry : pages) {
// Only delete what should be deleted.
if (configuration.getEntriesToImport() == null
|| configuration.getEntriesToImport().contains(xarEntry.getEntryName())) {
DocumentReference documentReference = new DocumentReference(xarEntry, wikiReference);
if (!configuration.isSkipMandatorytDocuments() || !isMandatoryDocument(documentReference)) {
documents.add(documentReference);
}
}
}
}
}