/*
* 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.script;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.apache.commons.lang3.StringUtils;
import org.xwiki.component.annotation.Component;
import org.xwiki.component.manager.ComponentLookupException;
import org.xwiki.extension.ExtensionId;
import org.xwiki.extension.InstalledExtension;
import org.xwiki.extension.job.ExtensionRequest;
import org.xwiki.extension.job.InstallRequest;
import org.xwiki.extension.repository.InstalledExtensionRepository;
import org.xwiki.extension.script.AbstractExtensionScriptService;
import org.xwiki.extension.version.Version;
import org.xwiki.extension.xar.XarExtensionExtension;
import org.xwiki.extension.xar.internal.handler.XarExtensionHandler;
import org.xwiki.extension.xar.internal.handler.packager.Packager;
import org.xwiki.extension.xar.internal.job.DiffXarJob;
import org.xwiki.extension.xar.internal.job.RepairXarJob;
import org.xwiki.extension.xar.internal.repository.XarInstalledExtension;
import org.xwiki.extension.xar.internal.repository.XarInstalledExtensionRepository;
import org.xwiki.extension.xar.job.diff.DiffXarJobStatus;
import org.xwiki.extension.xar.job.diff.DocumentVersionReference;
import org.xwiki.extension.xar.question.ConflictQuestion;
import org.xwiki.filter.FilterException;
import org.xwiki.job.Job;
import org.xwiki.job.JobException;
import org.xwiki.job.event.status.JobStatus;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.security.authorization.AuthorizationManager;
import org.xwiki.security.authorization.Right;
import org.xwiki.xar.XarException;
import com.xpn.xwiki.api.Document;
/**
* Various XAR oriented APIs for scripts.
*
* @version $Id: 798a8c4edd0bda3c4b581b46c2c38487d568b02d $
* @since 5.3M1
*/
@Component
@Named("extension.xar")
@Singleton
public class XarExtensionScriptService extends AbstractExtensionScriptService
{
/**
* The install request property that specifies which user triggered the XAR repair job.
*/
private static final String PROPERTY_USER_REFERENCE = "user.reference";
@Inject
private Packager packager;
@Inject
private AuthorizationManager genericAuthorization;
@Inject
@Named(XarExtensionHandler.TYPE)
private InstalledExtensionRepository installedXARs;
private XarInstalledExtensionRepository getXarInstalledExtensionRepository()
{
return (XarInstalledExtensionRepository) this.installedXARs;
}
/**
* Make sure the provided XAR extension properly is registered in the installed extensions index.
* <p>
* Start an asynchronous Job.
*
* @param id the extension identifier
* @param version the extension version
* @param wiki the wiki where the extension is installed
* @return the {@link Job} object which can be used to monitor the progress of the installation process, or
* {@code null} in case of failure
*/
public Job repairInstalledExtension(String id, String version, String wiki)
{
setError(null);
if (!this.authorization.hasAccess(Right.PROGRAM)) {
setError(new JobException("Need programming right to repair a XAR"));
return null;
}
String namespace = getWikiNamespace(wiki);
InstallRequest installRequest = new InstallRequest();
installRequest.setId(ExtensionRequest.getJobId(ExtensionRequest.JOBID_ACTION_PREFIX, id, namespace));
DocumentReference currentUserReference = this.documentAccessBridge.getCurrentUserReference();
if (currentUserReference != null) {
installRequest.setProperty(PROPERTY_USER_REFERENCE, currentUserReference);
// We set the string value because the extension repository doesn't know how to serialize/parse an extension
// property whose value is a DocumentReference, and adding support for it requires considerable refactoring
// because ExtensionPropertySerializers are not components (they are currently hard-coded).
installRequest.setExtensionProperty(PROPERTY_USER_REFERENCE, currentUserReference.toString());
}
installRequest.addExtension(new ExtensionId(id, version));
if (StringUtils.isNotBlank(namespace)) {
installRequest.addNamespace(namespace);
}
Job job = null;
try {
job = this.jobExecutor.execute(RepairXarJob.JOBTYPE, installRequest);
} catch (Exception e) {
setError(e);
}
return job;
}
/**
* Get the id of the previously (or currently) computed differences, in unified format, between the documents of an
* installed XAR extension and the document from the wiki..
*
* @param feature the identifier of a XAR extension (or one of its features)
* @param namespace the namespace where the XAR extension is installed
* @return the id of the {@link Job}
* @since 9.3RC1
*/
public List<String> getDiffJobId(String feature, String namespace)
{
return ExtensionRequest.getJobId(ExtensionRequest.JOBID_ACTION_PREFIX, feature, namespace);
}
/**
* Computes the differences, in unified format, between the documents of an installed XAR extension and the document
* from the wiki.
*
* @param feature the identifier of a XAR extension (or one of its features)
* @param wiki the wiki where the XAR extension is installed
* @return the {@link Job} object which can be used to monitor the progress while the differences are being
* computed, or {@code null} in case of failure
* @since 7.0RC1
*/
public Job diff(String feature, String wiki)
{
setError(null);
String namespace = getWikiNamespace(wiki);
InstallRequest installRequest = new InstallRequest();
installRequest.addExtension(new ExtensionId(feature, (Version) null));
if (namespace != null) {
installRequest.addNamespace(namespace);
}
installRequest.setId(getDiffJobId(feature, namespace));
try {
return this.jobExecutor.execute(DiffXarJob.JOB_TYPE, installRequest);
} catch (Exception e) {
setError(e);
return null;
}
}
private String getWikiNamespace(String wiki)
{
return StringUtils.isNotBlank(wiki) ? "wiki:" + wiki : null;
}
/**
* @return the possible conflicts
* @since 9.2RC1
*/
public ConflictQuestion.ConflictType[] getConflictTypes()
{
return ConflictQuestion.ConflictType.values();
}
/**
* @param reference the reference of the document to reset to its standard state (what it looks like in the
* extension XAR)
* @param extensionId the installed extension from which to get the standard version of the document
* @param jobId the id of the job which computed the diff if any
* @return true if the reset actually did something, false otherwise (any produced error can be accessed using
* {@link #getLastError()})
* @since 9.3RC1
*/
public boolean reset(DocumentReference reference, ExtensionId extensionId, List<String> jobId)
{
return reset(new DocumentVersionReference(reference, extensionId), jobId);
}
/**
* @param reference the reference of the document to reset to its standard state (what it looks like in the
* extension XAR)
* @param jobId the id of the job which computed the diff if any
* @return true if the reset actually did something, false otherwise (any produced error can be accessed using
* {@link #getLastError()})
* @since 9.3RC1
*/
public boolean reset(DocumentReference reference, List<String> jobId)
{
setError(null);
try {
// Only current author is allowed to modify (and so reset) the target document
this.genericAuthorization.checkAccess(Right.EDIT, this.xcontextProvider.get().getAuthorReference(),
reference);
// Reset the document in the DB
this.packager.reset(reference, this.xcontextProvider.get().getUserReference());
// Update the existing job status if any
if (jobId != null) {
JobStatus jobStatus = getJobStatus(jobId);
if (jobStatus != null && jobStatus instanceof DiffXarJobStatus) {
((DiffXarJobStatus) jobStatus).reset(reference);
}
}
return true;
} catch (Exception e) {
setError(e);
}
return false;
}
/**
* @param reference the reference of the document
* @return the installed XAR extensions in which this document can be found
* @since 9.3RC1
*/
public Collection<InstalledExtension> getInstalledExtensions(DocumentReference reference)
{
return safe((Collection) getXarInstalledExtensionRepository().getXarInstalledExtensions(reference));
}
/**
* @param reference the reference of the document
* @return a Document instance of passed document when extracted from the standard extension matching this
* reference. Null if none could be found.
* @throws XarExtensionExtension when failing to get the document
* @since 9.3RC1
*/
public Document getInstalledExtensionDocument(DocumentReference reference) throws XarExtensionExtension
{
try {
return safe(this.packager.getXWikiDocument(reference));
} catch (FilterException | IOException | ComponentLookupException | XarException e) {
throw new XarExtensionExtension(String.format("Failed to get standard version of document [%s]", reference),
e);
}
}
/**
* @param reference the reference of the document
* @param extensionId the id of the extension from which to get the standard version of the document
* @return a Document instance of passed document when extracted from the standard extension matching this
* reference. Null if none could be found.
* @throws XarExtensionExtension when failing to get the document
* @since 9.3RC1
*/
public Document getInstalledExtensionDocument(DocumentReference reference, ExtensionId extensionId)
throws XarExtensionExtension
{
try {
return safe(this.packager.getXWikiDocument(reference, extensionId));
} catch (FilterException | IOException | ComponentLookupException | XarException e) {
throw new XarExtensionExtension(
String.format("Failed to get standard version of document [%s] from extension with id [%s]", reference,
extensionId),
e);
}
}
/**
* @param reference the reference of the document
* @param extension the extension from which to get the standard version of the document
* @return a Document instance of passed document when extracted from the standard extension matching this
* reference. Null if none could be found.
* @throws XarExtensionExtension when failing to get the document
* @since 9.3RC1
*/
public Document getInstalledExtensionDocument(DocumentReference reference, XarInstalledExtension extension)
throws XarExtensionExtension
{
try {
return safe(this.packager.getXWikiDocument(reference, extension));
} catch (FilterException | IOException | ComponentLookupException | XarException e) {
throw new XarExtensionExtension(String.format(
"Failed to get standard version of document [%s] from extension [%s]", reference, extension), e);
}
}
}