/* * 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.util.Collection; import java.util.Collections; import java.util.Map; import javax.inject.Inject; import javax.inject.Named; import org.xwiki.component.annotation.Component; import org.xwiki.extension.Extension; import org.xwiki.extension.ExtensionDependency; import org.xwiki.extension.ExtensionId; import org.xwiki.extension.InstallException; import org.xwiki.extension.InstalledExtension; import org.xwiki.extension.LocalExtension; import org.xwiki.extension.ResolveException; import org.xwiki.extension.event.ExtensionInstallFailedEvent; import org.xwiki.extension.event.ExtensionInstalledEvent; import org.xwiki.extension.event.ExtensionInstallingEvent; import org.xwiki.extension.internal.ExtensionUtils; import org.xwiki.extension.job.InstallRequest; import org.xwiki.extension.job.internal.AbstractExtensionJob; import org.xwiki.extension.repository.CoreExtensionRepository; import org.xwiki.extension.repository.ExtensionRepositoryManager; import org.xwiki.extension.repository.InstalledExtensionRepository; import org.xwiki.extension.repository.LocalExtensionRepository; import org.xwiki.extension.repository.LocalExtensionRepositoryException; import org.xwiki.extension.xar.internal.handler.XarExtensionHandler; import org.xwiki.job.DefaultJobStatus; import org.xwiki.job.Request; import org.xwiki.logging.marker.BeginTranslationMarker; import org.xwiki.logging.marker.EndTranslationMarker; /** * Make sure the provided XAR extension properly is registered in the installed extensions index. * * @version $Id: 8e07d596593fd658b23c17fa8977db88a4dd7378 $ * @since 4.3M1 */ @Component @Named(RepairXarJob.JOBTYPE) public class RepairXarJob extends AbstractExtensionJob<InstallRequest, DefaultJobStatus<InstallRequest>> { /** * The id of the job. */ public static final String JOBTYPE = "repairxar"; private static final BeginTranslationMarker LOG_REPAIR_BEGIN = new BeginTranslationMarker("extension.xar.log.repair.begin"); private static final BeginTranslationMarker LOG_REPAIR_NAMESPACE_BEGIN = new BeginTranslationMarker("extension.xar.log.repair.begin.namespace"); private static final EndTranslationMarker LOG_REPAIR_END = new EndTranslationMarker("extension.xar.log.repair.end"); private static final EndTranslationMarker LOG_REPAIR_END_NAMESPACE = new EndTranslationMarker("extension.xar.log.repair.end.namespace"); /** * Used to resolve extensions to install. */ @Inject protected ExtensionRepositoryManager repositoryManager; /** * Used to set a local extension as installed. */ @Inject private InstalledExtensionRepository installedRepository; /** * Used to store downloaded extensions. */ @Inject private LocalExtensionRepository localRepository; @Inject private CoreExtensionRepository coreRepository; @Override public String getType() { return JOBTYPE; } @Override protected InstallRequest castRequest(Request request) { InstallRequest installRequest; if (request instanceof InstallRequest) { installRequest = (InstallRequest) request; } else { installRequest = new InstallRequest(request); } return installRequest; } @Override protected void runInternal() throws Exception { this.progressManager.pushLevelProgress(getRequest().getExtensions().size(), this); try { for (ExtensionId extensionId : getRequest().getExtensions()) { this.progressManager.startStep(this); if (getRequest().getNamespaces() != null) { this.progressManager.pushLevelProgress(getRequest().getNamespaces().size(), this); try { for (String namespace : getRequest().getNamespaces()) { this.progressManager.startStep(this); repairExtension(extensionId, namespace, false, Collections.emptyMap()); this.progressManager.endStep(this); } } finally { this.progressManager.popLevelProgress(this); } } else { repairExtension(extensionId, null, false, Collections.emptyMap()); } this.progressManager.endStep(this); } } finally { this.progressManager.popLevelProgress(this); } } /** * @param extensionId the extension unique identifier * @return the stored local extension * @throws InstallException failed to store extension */ private LocalExtension getLocalXARExtension(ExtensionId extensionId) throws InstallException { LocalExtension localExtension = this.localRepository.getLocalExtension(extensionId); if (localExtension == null) { this.progressManager.pushLevelProgress(2, this); try { this.progressManager.startStep(this); Extension extension = this.repositoryManager.resolve(extensionId); this.progressManager.endStep(this); this.progressManager.startStep(this); if (extension.getType().equals(XarExtensionHandler.TYPE)) { localExtension = this.localExtensionRepository.storeExtension(extension); } this.progressManager.endStep(this); } catch (ResolveException e) { throw new InstallException("Failed to find extension", e); } catch (LocalExtensionRepositoryException e) { throw new InstallException("Failed save extension in local repository", e); } finally { this.progressManager.popLevelProgress(this); } } else if (!localExtension.getType().equals(XarExtensionHandler.TYPE)) { localExtension = null; } return localExtension; } /** * @param extensionId the unique extension identifier * @param namespace the namespace where to install extension * @param dependency indicate of the extension is installed as a dependency of another * @param managedDependencies the managed dependencies * @throws InstallException failed to repair extension */ private void repairExtension(ExtensionId extensionId, String namespace, boolean dependency, Map<String, ExtensionDependency> managedDependencies) throws InstallException { if (this.installedRepository.getInstalledExtension(extensionId.getId(), namespace) != null) { this.logger.debug("Extension [{}] already installed on namespace [{}]", extensionId.getId(), namespace); return; } if (getRequest().isVerbose()) { if (namespace != null) { this.logger.info(LOG_REPAIR_NAMESPACE_BEGIN, "Repairing XAR extension [{}] on namespace [{}]", extensionId, namespace); } else { this.logger.info(LOG_REPAIR_BEGIN, "Repairing XAR extension [{}] on all namespaces", extensionId, namespace); } } this.progressManager.pushLevelProgress(2, this); try { this.progressManager.startStep(this); LocalExtension localExtension = getLocalXARExtension(extensionId); this.progressManager.endStep(this); this.progressManager.startStep(this); if (localExtension != null) { repairExtension(localExtension, namespace, dependency, managedDependencies); } } finally { if (getRequest().isVerbose()) { if (namespace != null) { this.logger.info(LOG_REPAIR_END_NAMESPACE, "Done repairing XAR extension [{}] on namespace [{}]", extensionId, namespace); } else { this.logger.info(LOG_REPAIR_END, "Done repairing XAR extension [{}] on all namespaces", extensionId, namespace); } } this.progressManager.popLevelProgress(this); } } /** * @param localExtension the local extension to install * @param namespace the namespace where to install extension * @param dependency indicate of the extension is installed as a dependency of another * @param managedDependencies the managed dependencies * @throws InstallException failed to repair extension */ private void repairExtension(LocalExtension localExtension, String namespace, boolean dependency, Map<String, ExtensionDependency> managedDependencies) throws InstallException { this.progressManager.pushLevelProgress(2, this); this.observationManager.notify(new ExtensionInstallingEvent(localExtension.getId(), namespace), localExtension); InstalledExtension installedExtension = null; try { this.progressManager.startStep(this); Collection<? extends ExtensionDependency> dependencies = localExtension.getDependencies(); if (!dependencies.isEmpty()) { this.progressManager.pushLevelProgress(dependencies.size(), dependencies); try { for (ExtensionDependency extensionDependency : dependencies) { this.progressManager.startStep(dependencies); // Replace with managed dependency if any ExtensionDependency resolvedDependency = ExtensionUtils.getDependency(extensionDependency, managedDependencies, localExtension); repairDependency(resolvedDependency, namespace, ExtensionUtils.append(managedDependencies, localExtension)); this.progressManager.endStep(dependencies); } } finally { this.progressManager.popLevelProgress(dependencies); } } this.progressManager.endStep(this); this.progressManager.startStep(this); installedExtension = this.installedRepository.installExtension(localExtension, namespace, dependency); } finally { if (installedExtension != null) { this.observationManager.notify(new ExtensionInstalledEvent(installedExtension.getId(), namespace), installedExtension); } else { this.observationManager.notify(new ExtensionInstallFailedEvent(localExtension.getId(), namespace), localExtension); } this.progressManager.popLevelProgress(this); } } /** * @param extensionDependency the extension dependency to install * @param namespace the namespace where to install extension * @param managedDependencies the managed dependencies */ private void repairDependency(ExtensionDependency extensionDependency, String namespace, Map<String, ExtensionDependency> managedDependencies) { // Skip core extensions if (this.coreRepository.getCoreExtension(extensionDependency.getId()) == null) { // TODO: take into account managed dependencies if (extensionDependency.getVersionConstraint().getVersion() == null) { this.logger.warn( "Can't repair extension dependency [{}] with version range ([{}])" + " since there is no way to know what has been installed", extensionDependency.getId(), extensionDependency.getVersionConstraint()); } else { try { repairExtension( new ExtensionId(extensionDependency.getId(), extensionDependency.getVersionConstraint().getVersion()), namespace, true, managedDependencies); } catch (InstallException e) { this.logger.warn("Failed to repair dependency [{}]", extensionDependency, e); } } } } }