org.nuxeo.connect.packages.PackageManagerImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.nuxeo.connect.packages.PackageManagerImpl.java

Source

/*
 * (C) Copyright 2006-2017 Nuxeo SA (http://nuxeo.com/) and others.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Contributors:
 *     Nuxeo - initial API and implementation
 *     Yannis JULIENNE
 *
 */

package org.nuxeo.connect.packages;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.connect.NuxeoConnectClient;
import org.nuxeo.connect.connector.ConnectServerError;
import org.nuxeo.connect.data.DownloadablePackage;
import org.nuxeo.connect.data.DownloadingPackage;
import org.nuxeo.connect.downloads.ConnectDownloadManager;
import org.nuxeo.connect.packages.dependencies.CUDFHelper;
import org.nuxeo.connect.packages.dependencies.DependencyException;
import org.nuxeo.connect.packages.dependencies.DependencyResolution;
import org.nuxeo.connect.packages.dependencies.DependencyResolver;
import org.nuxeo.connect.packages.dependencies.LegacyDependencyResolver;
import org.nuxeo.connect.packages.dependencies.P2CUDFDependencyResolver;
import org.nuxeo.connect.packages.dependencies.TargetPlatformFilterHelper;
import org.nuxeo.connect.registration.ConnectRegistrationService;
import org.nuxeo.connect.update.LocalPackage;
import org.nuxeo.connect.update.Package;
import org.nuxeo.connect.update.PackageDependency;
import org.nuxeo.connect.update.PackageException;
import org.nuxeo.connect.update.PackageType;
import org.nuxeo.connect.update.PackageUpdateService;
import org.nuxeo.connect.update.PackageVisibility;
import org.nuxeo.connect.update.Version;
import org.nuxeo.connect.update.VersionRange;
import org.nuxeo.connect.update.task.Task;

/**
 * Nuxeo Component that implements {@link PackageManager}
 *
 * @author <a href="mailto:td@nuxeo.com">Thierry Delprat</a>
 */
@SuppressWarnings("deprecation")
public class PackageManagerImpl implements PackageManager {

    protected static final Log log = LogFactory.getLog(PackageManagerImpl.class);

    protected List<PackageSource> localSources = new ArrayList<>();

    protected List<PackageSource> remoteSources = new ArrayList<>();

    protected List<String> sourcesNames = new ArrayList<>();

    /**
     * @deprecated Since 1.4.25. Unused.
     */
    @Deprecated
    protected Map<String, DownloadablePackage> cachedPackageList = null;

    protected DependencyResolver resolver;

    @Override
    public List<PackageSource> getAllSources() {
        List<PackageSource> allSources = new ArrayList<>();
        allSources.addAll(remoteSources);
        allSources.addAll(localSources);
        return allSources;
    }

    public PackageManagerImpl() {
        registerSource(new RemotePackageSource(), false);
        registerSource(new DownloadingPackageSource(), true);
        registerSource(new LocalPackageSource(), true);
        setResolver(DEFAULT_DEPENDENCY_RESOLVER);
    }

    /**
     * @since 1.4
     */
    @Override
    public void setResolver(String resolverType) {
        if (P2CUDF_DEPENDENCY_RESOLVER.equals(resolverType)) {
            resolver = new P2CUDFDependencyResolver(this);
        } else if (LEGACY_DEPENDENCY_RESOLVER.equals(resolverType)) {
            resolver = new LegacyDependencyResolver(this);
        } else {
            log.warn("Resolver " + resolverType + "is not supported - fallback on default resolver "
                    + DEFAULT_DEPENDENCY_RESOLVER);
            resolver = new P2CUDFDependencyResolver(this);
        }
    }

    public void resetSources() {
        localSources.clear();
        remoteSources.clear();
        sourcesNames.clear();
        if (cachedPackageList != null) {
            cachedPackageList.clear();
        }
    }

    /**
     * Merge packages, keeping only greater versions
     */
    protected List<DownloadablePackage> doMergePackages(List<PackageSource> sources, PackageType type,
            String targetPlatform) {
        List<DownloadablePackage> allPackages = getAllPackages(sources, type, targetPlatform);
        Map<String, Map<String, DownloadablePackage>> packagesByIdAndTargetPlatform = new HashMap<>();
        for (DownloadablePackage pkg : allPackages) {
            String[] targetPlatforms = targetPlatform != null ? new String[] { targetPlatform }
                    : pkg.getTargetPlatforms();
            if (targetPlatforms == null) { // if the package doesn't specify any target platform
                targetPlatforms = new String[] { null };
            }
            for (String tp : targetPlatforms) {
                Map<String, DownloadablePackage> packagesById = packagesByIdAndTargetPlatform.get(tp);
                if (packagesById == null) {
                    packagesById = new HashMap<>();
                    packagesByIdAndTargetPlatform.put(tp, packagesById);
                }
                String key = pkg.getId();
                if (packagesById.containsKey(key)) {
                    if (pkg.getVersion().greaterThan(packagesById.get(key).getVersion())) {
                        packagesById.put(key, pkg);
                    }
                } else {
                    packagesById.put(key, pkg);
                }
            }
        }
        List<DownloadablePackage> result = new ArrayList<>();
        for (Map<String, DownloadablePackage> packagesById : packagesByIdAndTargetPlatform.values()) {
            for (DownloadablePackage pkg : packagesById.values()) {
                if (!result.contains(pkg)) {
                    result.add(pkg);
                }
            }
        }
        Collections.sort(result, new PackageComparator());
        return result;
    }

    /**
     * @since 1.4
     * @return All downloadable packages from given sources filtered on type if not null
     */
    protected List<DownloadablePackage> getAllPackages(List<PackageSource> sources, PackageType type) {
        return getAllPackages(sources, type, null);
    }

    /**
     * @since 1.4
     * @return All downloadable packages from given sources, optionally filtered on type and/or target platform if not
     *         null
     */
    protected List<DownloadablePackage> getAllPackages(List<PackageSource> sources, PackageType type,
            String targetPlatform) {
        Map<String, DownloadablePackage> packagesById = getAllPackagesByID(sources, type, targetPlatform);
        return new ArrayList<>(packagesById.values());
    }

    /**
     * @since 1.4
     * @return a Map of all packages from given sources filtered on type if not null
     */
    protected Map<String, DownloadablePackage> getAllPackagesByID(List<PackageSource> sources, PackageType type) {
        return getAllPackagesByID(sources, type, null);
    }

    /**
     * @since 1.4
     * @return a Map of all packages from given sources, optionally filtered on type and/or target platform if not null
     */
    protected Map<String, DownloadablePackage> getAllPackagesByID(List<PackageSource> sources, PackageType type,
            String targetPlatform) {
        Map<String, DownloadablePackage> packagesById = new HashMap<>();
        for (PackageSource source : sources) {
            List<DownloadablePackage> packages = null;
            if (type == null) {
                packages = source.listPackages();
            } else {
                packages = source.listPackages(type);
            }
            for (DownloadablePackage pkg : packages) {
                if (TargetPlatformFilterHelper.isCompatibleWithTargetPlatform(pkg, targetPlatform)) {
                    packagesById.put(pkg.getId(), pkg);
                }
            }
        }
        return packagesById;
    }

    @Override
    public Map<String, DownloadablePackage> getAllPackagesByID() {
        return getAllPackagesByID(getAllSources(), null);
    }

    @Override
    public Map<String, List<DownloadablePackage>> getAllPackagesByName() {
        return getAllPackagesByName(getAllSources(), null);
    }

    /**
     * @since 1.4
     * @return a Map of all packages from given sources filtered on type if not null
     */
    protected Map<String, List<DownloadablePackage>> getAllPackagesByName(List<PackageSource> sources,
            PackageType type) {
        Map<String, List<DownloadablePackage>> packagesByName = new HashMap<>();
        for (PackageSource source : sources) {
            List<DownloadablePackage> packages = null;
            if (type == null) {
                packages = source.listPackages();
            } else {
                packages = source.listPackages(type);
            }
            for (DownloadablePackage pkg : packages) {
                List<DownloadablePackage> pkgsForName;
                if (!packagesByName.containsKey(pkg.getName())) {
                    pkgsForName = new ArrayList<>();
                    packagesByName.put(pkg.getName(), pkgsForName);
                } else {
                    pkgsForName = packagesByName.get(pkg.getName());
                }
                pkgsForName.add(pkg);
            }
        }
        return packagesByName;
    }

    @Override
    public List<DownloadablePackage> findRemotePackages(String packageName) {
        List<DownloadablePackage> pkgs = new ArrayList<>();
        for (PackageSource source : remoteSources) {
            pkgs.addAll(source.listPackagesByName(packageName));
        }
        return pkgs;
    }

    @Override
    public List<DownloadablePackage> findLocalPackages(String packageName) {
        List<DownloadablePackage> pkgs = new ArrayList<>();
        for (PackageSource source : localSources) {
            for (DownloadablePackage pkg : source.listPackages()) {
                if (pkg.getName().equals(packageName)) {
                    pkgs.add(pkg);
                }
            }
        }
        return pkgs;
    }

    @Override
    public List<Version> findLocalPackageVersions(String packageName) {
        List<Version> versions = new ArrayList<>();
        for (PackageSource source : localSources) {
            for (DownloadablePackage pkg : source.listPackages()) {
                if (pkg.getName().equals(packageName)) {
                    versions.add(pkg.getVersion());
                }
            }
        }
        return versions;
    }

    @Override
    public List<Version> findLocalPackageInstalledVersions(String packageName) {
        List<Version> versions = new ArrayList<>();
        for (PackageSource source : localSources) {
            for (DownloadablePackage pkg : source.listPackages()) {
                if (pkg.getName().equals(packageName) && pkg.getPackageState().isInstalled()) {
                    versions.add(pkg.getVersion());
                }
            }
        }
        return versions;
    }

    @Override
    public DownloadablePackage findPackageById(String packageId) {
        DownloadablePackage pkg = findPackageById(packageId, localSources);
        if (pkg == null) {
            pkg = findPackageById(packageId, remoteSources);
        }
        return pkg;
    }

    @Override
    public DownloadablePackage findRemotePackageById(String packageId) {
        return findPackageById(packageId, remoteSources);
    }

    @Override
    public DownloadablePackage findLocalPackageById(String packageId) {
        return findPackageById(packageId, localSources);
    }

    /**
     * @since 1.4
     * @param packageId Package ID to look for in {@code sources}
     * @param sources
     * @return The package searched by ID or null if not found.
     */
    protected DownloadablePackage findPackageById(String packageId, List<PackageSource> sources) {
        for (PackageSource source : sources) {
            DownloadablePackage pkg = source.getPackageById(packageId);
            if (pkg != null) {
                return pkg;
            }
        }
        return null;
    }

    @Override
    public List<Version> getPreferedVersions(String pkgName) {
        List<Version> versions = new ArrayList<>();
        List<Version> installedVersions = new ArrayList<>();
        List<Version> localVersions = new ArrayList<>();
        List<Version> remoteVersions = new ArrayList<>();
        for (PackageSource source : localSources) {
            for (DownloadablePackage pkg : source.listPackagesByName(pkgName)) {
                if (pkg.getPackageState().isInstalled()) {
                    installedVersions.add(pkg.getVersion());
                } else {
                    localVersions.add(pkg.getVersion());
                }
            }
        }
        for (PackageSource source : remoteSources) {
            for (DownloadablePackage pkg : source.listPackagesByName(pkgName)) {
                remoteVersions.add(pkg.getVersion());
            }
        }
        Collections.sort(localVersions);
        Collections.sort(remoteVersions);
        versions.addAll(installedVersions);
        versions.addAll(localVersions);
        versions.addAll(remoteVersions);
        return versions;
    }

    @Override
    public List<Version> getAvailableVersion(String pkgName, VersionRange range, String targetPlatform) {
        List<Version> versions = new ArrayList<>();
        for (PackageSource source : getAllSources()) {
            for (DownloadablePackage pkg : source.listPackagesByName(pkgName)) {
                if (range.matchVersion(pkg.getVersion())
                        && TargetPlatformFilterHelper.isCompatibleWithTargetPlatform(pkg, targetPlatform)) {
                    if (!versions.contains(pkg.getVersion())) {
                        versions.add(pkg.getVersion());
                    }
                }
            }
        }
        return versions;
    }

    @Override
    public List<DownloadablePackage> listPackages() {
        return listPackages(null, null);
    }

    @Override
    public List<DownloadablePackage> listPackages(String targetPlatform) {
        return listPackages(null, targetPlatform);
    }

    @Override
    public List<DownloadablePackage> listPackages(PackageType type) {
        return listPackages(type, null);
    }

    @Override
    public List<DownloadablePackage> listPackages(PackageType pkgType, String targetPlatform) {
        return doMergePackages(getAllSources(), pkgType, targetPlatform);
    }

    @Override
    public List<DownloadablePackage> searchPackages(String searchExpr) {
        return null;
    }

    @Override
    public void registerSource(PackageSource source, boolean local) {
        String name = source.getName();
        if (!sourcesNames.contains(name)) {
            if (local) {
                localSources.add(source);
            } else {
                remoteSources.add(source);
            }
        } else {
            log.warn("Already registered a package source named " + name);
        }
    }

    @Override
    public List<DownloadablePackage> listInstalledPackages() {
        List<DownloadablePackage> res = new ArrayList<>();
        for (PackageSource source : localSources) {
            for (DownloadablePackage pkg : source.listPackages()) {
                if (pkg.getPackageState().isInstalled()) {
                    res.add(pkg);
                }
            }
        }
        Collections.sort(res, new PackageComparator());
        return res;
    }

    @Override
    public List<String> listInstalledPackagesNames(PackageType pkgType) {
        List<DownloadablePackage> installedPackages = listInstalledPackages();
        // filter on type and collect names
        List<String> installedPackagesNames = installedPackages.stream()
                .filter(pkg -> (pkgType == null || pkg.getType() == pkgType)).map(DownloadablePackage::getName)
                .collect(Collectors.toList());

        return installedPackagesNames;
    }

    @Override
    public List<String> listHotfixesNames(String targetPlatform, boolean allowSNAPSHOT) {
        List<DownloadablePackage> hotFixes = listPackages(PackageType.HOT_FIX, targetPlatform);
        // filter on snapshots and collect unique names
        List<String> hotFixesNames = hotFixes.stream()
                .filter(pkg -> (allowSNAPSHOT || !pkg.getVersion().isSnapshot())).map(DownloadablePackage::getName)
                .distinct().collect(Collectors.toList());
        return hotFixesNames;
    }

    @Override
    public List<DownloadablePackage> listRemotePackages() {
        return listRemotePackages(null, null);
    }

    @Override
    public List<DownloadablePackage> listRemotePackages(PackageType pkgType) {
        return listRemotePackages(pkgType, null);
    }

    @Override
    public List<DownloadablePackage> listRemotePackages(PackageType pkgType, String targetPlatform) {
        return doMergePackages(remoteSources, pkgType, targetPlatform);
    }

    @Override
    public List<DownloadablePackage> listLocalPackages() {
        return listLocalPackages(null);
    }

    @Override
    public List<DownloadablePackage> listLocalPackages(PackageType type) {
        List<DownloadablePackage> result = new ArrayList<>();
        List<String> pkgIds = new ArrayList<>();
        for (PackageSource source : localSources) {
            List<DownloadablePackage> pkgs = source.listPackages(type);
            for (DownloadablePackage pkg : pkgs) {
                if (!pkgIds.contains(pkg.getId())) {
                    pkgIds.add(pkg.getId());
                    result.add(pkg);
                }
            }
        }
        Collections.sort(result, new PackageComparator());
        return result;
    }

    @Override
    public List<DownloadablePackage> listUpdatePackages() {
        return listUpdatePackages(null, null);
    }

    @Override
    public List<DownloadablePackage> listUpdatePackages(PackageType type) {
        return listUpdatePackages(type, null);
    }

    @Override
    public List<DownloadablePackage> listUpdatePackages(String targetPlatform) {
        return listUpdatePackages(null, targetPlatform);
    }

    @Override
    public List<DownloadablePackage> listUpdatePackages(PackageType type, String targetPlatform) {
        List<String> installedPackagesNames = listInstalledPackagesNames(type);
        List<String> hotfixesNames = null;
        if (type == null || type == PackageType.HOT_FIX) {
            // list last version of available hot-fixes too
            hotfixesNames = listHotfixesNames(targetPlatform, CUDFHelper.defaultAllowSNAPSHOT);
            hotfixesNames.removeAll(installedPackagesNames);
        }
        DependencyResolution resolution = resolveDependencies(hotfixesNames, null, installedPackagesNames,
                targetPlatform, CUDFHelper.defaultAllowSNAPSHOT);

        List<String> toUpdateIds = resolution.getOrderedPackageIdsToInstall();
        List<DownloadablePackage> toUpdate = toUpdateIds.stream().map(this::getPackage)
                .collect(Collectors.toList());

        return toUpdate;
    }

    @Override
    public List<DownloadablePackage> listPrivatePackages(PackageType pkgType, String targetPlatform) {
        List<DownloadablePackage> allPackages = getAllPackages(getAllSources(), pkgType, targetPlatform);
        Collections.sort(allPackages, new PackageComparator());
        List<DownloadablePackage> allPrivatePackages = new ArrayList<>();
        for (DownloadablePackage downloadablePackage : allPackages) {
            if (downloadablePackage.getVisibility() == PackageVisibility.PRIVATE) {
                allPrivatePackages.add(downloadablePackage);
            }
        }
        return allPrivatePackages;
    }

    @Override
    public List<DownloadablePackage> listPrivatePackages(String targetPlatform) {
        return listPrivatePackages(null, targetPlatform);
    }

    @Override
    public DownloadingPackage download(String packageId) throws ConnectServerError {
        ConnectRegistrationService crs = NuxeoConnectClient.getConnectRegistrationService();
        return crs.getConnector().getDownload(packageId);
    }

    @Override
    public List<DownloadingPackage> download(List<String> packageIds) throws ConnectServerError {
        List<DownloadingPackage> downloadings = new ArrayList<>();
        for (String packageId : packageIds) {
            DownloadingPackage download = download(packageId);
            if (download != null) {
                downloadings.add(download);
            } else {
                log.error("Download failed for " + packageId);
            }
        }
        return downloadings;
    }

    @Override
    public void install(String packageId, Map<String, String> params) throws PackageException {
        PackageUpdateService pus = NuxeoConnectClient.getPackageUpdateService();
        LocalPackage pkg = pus.getPackage(packageId);
        Task installationTask = pkg.getInstallTask();
        installationTask.validate();
        installationTask.run(params);
    }

    @Override
    public void install(List<String> packageIds, Map<String, String> params) throws PackageException {
        for (String packageId : packageIds) {
            install(packageId, params);
        }
    }

    /**
     * @deprecated Since 1.4.25. Unused.
     */
    @Deprecated
    protected void invalidateCache() {
        cachedPackageList = null;
    }

    /**
     * @deprecated Since 1.4.25. Unused.
     */
    @Deprecated
    protected Map<String, DownloadablePackage> getCachedPackageList() {
        if (cachedPackageList == null) {
            cachedPackageList = new HashMap<>();
        }
        for (DownloadablePackage pkg : listPackages()) {
            cachedPackageList.put(pkg.getId(), pkg);
        }
        return cachedPackageList;
    }

    protected DownloadablePackage getPkgInList(List<DownloadablePackage> pkgs, String pkgId) {
        for (DownloadablePackage pkg : pkgs) {
            if (pkgId.equals(pkg.getId())) {
                return pkg;
            }
        }
        return null;
    }

    @Override
    public DownloadablePackage getLocalPackage(String pkgId) {
        List<DownloadablePackage> pkgs = listLocalPackages();
        return getPkgInList(pkgs, pkgId);
    }

    @Override
    public DownloadablePackage getRemotePackage(String pkgId) {
        List<DownloadablePackage> pkgs = listRemotePackages();
        return getPkgInList(pkgs, pkgId);
    }

    @Override
    public DownloadablePackage getPackage(String pkgId) {
        // Merge is an issue for P2CUDFDependencyResolver
        List<DownloadablePackage> pkgs = listAllPackages();
        DownloadablePackage pkg = getPkgInList(pkgs, pkgId);
        if (pkg == null) {
            List<DownloadablePackage> studioPkgs = listAllStudioRemotePackages();
            pkg = getPkgInList(studioPkgs, pkgId);
        }
        return pkg;
    }

    @Deprecated
    @Override
    public List<DownloadablePackage> listRemoteOrLocalPackages() {
        return listRemoteOrLocalPackages(null, null);
    }

    @Deprecated
    @Override
    public List<DownloadablePackage> listRemoteOrLocalPackages(PackageType pkgType) {
        return listRemoteOrLocalPackages(pkgType, null);
    }

    @Override
    public List<DownloadablePackage> listRemoteOrLocalPackages(String targetPlatform) {
        return listRemoteOrLocalPackages(null, targetPlatform);
    }

    @Override
    public List<DownloadablePackage> listRemoteOrLocalPackages(PackageType pkgType, String targetPlatform) {
        List<DownloadablePackage> result = new ArrayList<>();
        List<DownloadablePackage> all = listPackages(pkgType, targetPlatform);
        List<DownloadablePackage> remotes = listRemotePackages(pkgType, targetPlatform);
        // Return only packages which are available on remote sources
        for (DownloadablePackage pkg : all) {
            for (DownloadablePackage remote : remotes) {
                if (remote.getId().equals(pkg.getId())) {
                    result.add(pkg);
                    break;
                }
            }
        }
        return result;
    }

    @Override
    public List<DownloadablePackage> listAllStudioRemoteOrLocalPackages() {
        List<DownloadablePackage> remote = listRemoteAssociatedStudioPackages();
        List<DownloadablePackage> local = listLocalPackages(PackageType.STUDIO);
        List<DownloadablePackage> result = new ArrayList<>();
        result.addAll(local);
        REMOTE: for (DownloadablePackage rpkg : remote) {
            for (DownloadablePackage lpkg : local) {
                if (lpkg.getId().equals(rpkg.getId())) {
                    continue REMOTE;
                }
            }
            result.add(rpkg);
        }
        Collections.sort(result, new PackageComparator());
        return result;
    }

    @Deprecated
    @Override
    public List<DownloadablePackage> listOnlyRemotePackages() {
        return listOnlyRemotePackages(null, null);
    }

    @Deprecated
    @Override
    public List<DownloadablePackage> listOnlyRemotePackages(PackageType pkgType) {
        return listOnlyRemotePackages(pkgType, null);
    }

    @Override
    public List<DownloadablePackage> listOnlyRemotePackages(String targetPlatform) {
        return listOnlyRemotePackages(null, targetPlatform);
    }

    @Override
    public List<DownloadablePackage> listOnlyRemotePackages(PackageType pkgType, String targetPlatform) {
        List<DownloadablePackage> result = listRemotePackages(pkgType, targetPlatform);
        List<DownloadablePackage> local = listLocalPackages(pkgType);
        for (DownloadablePackage pkg : local) {
            for (DownloadablePackage remote : result) {
                if (remote.getName().equals(pkg.getName())) {
                    result.remove(remote);
                    break;
                }
            }
        }
        return result;
    }

    @Override
    public List<DownloadablePackage> listAllStudioRemotePackages() {
        return listRemotePackages(PackageType.STUDIO);
    }

    @Override
    public List<DownloadablePackage> listRemoteAssociatedStudioPackages() {
        List<DownloadablePackage> result = new ArrayList<>();
        List<String> pkgIds = new ArrayList<>();
        for (PackageSource source : remoteSources) {
            List<DownloadablePackage> pkgs = source.listStudioPackages();
            for (DownloadablePackage pkg : pkgs) {
                if (!pkgIds.contains(pkg.getId())) {
                    pkgIds.add(pkg.getId());
                    result.add(pkg);
                }
            }
        }
        return result;
    }

    @Override
    public void flushCache() {
        for (PackageSource source : getAllSources()) {
            source.flushCache();
        }
    }

    @Override
    @Deprecated
    public DependencyResolution resolveDependencies(String pkgId, String targetPlatform) {
        try {
            DependencyResolution resolution = resolver.resolve(pkgId, targetPlatform);
            log.debug(beforeAfterResolutionToString(resolution));
            return resolution;
        } catch (DependencyException e) {
            return new DependencyResolution(e);
        }
    }

    /**
     * @return Packages list before and after given resolution
     * @since 1.4.13
     */
    public String beforeAfterResolutionToString(DependencyResolution resolution) {
        StringBuilder sb = new StringBuilder();
        sb.append("\nBefore: " + listInstalledPackages());
        List<String> after = new ArrayList<>();
        after.addAll(resolution.getUnchangedPackageIds());
        after.addAll(resolution.getInstallPackageIds());
        Collections.sort(after);
        sb.append("\nAfter:  " + after);
        return sb.toString();
    }

    /**
     * @since 1.4
     * @see PackageManager#resolveDependencies(List, List, List, String, boolean)
     */
    @Override
    public DependencyResolution resolveDependencies(List<String> pkgInstall, List<String> pkgRemove,
            List<String> pkgUpgrade, String targetPlatform) {
        return resolveDependencies(pkgInstall, pkgRemove, pkgUpgrade, targetPlatform,
                CUDFHelper.defaultAllowSNAPSHOT, true);
    }

    @Override
    public DependencyResolution resolveDependencies(List<String> pkgInstall, List<String> pkgRemove,
            List<String> pkgUpgrade, String targetPlatform, boolean allowSNAPSHOT) {
        return resolveDependencies(pkgInstall, pkgRemove, pkgUpgrade, targetPlatform, allowSNAPSHOT, true);
    }

    @Override
    public DependencyResolution resolveDependencies(List<String> pkgInstall, List<String> pkgRemove,
            List<String> pkgUpgrade, String targetPlatform, boolean allowSNAPSHOT, boolean doKeep) {
        return resolveDependencies(pkgInstall, pkgRemove, pkgUpgrade, targetPlatform, allowSNAPSHOT, doKeep, false);
    }

    @Override
    public DependencyResolution resolveDependencies(List<String> pkgInstall, List<String> pkgRemove,
            List<String> pkgUpgrade, String targetPlatform, boolean allowSNAPSHOT, boolean doKeep,
            boolean isSubResolution) {
        try {
            DependencyResolution resolution = resolver.resolve(pkgInstall, pkgRemove, pkgUpgrade, targetPlatform,
                    allowSNAPSHOT, doKeep, isSubResolution);

            log.debug(beforeAfterResolutionToString(resolution));
            return resolution;
        } catch (DependencyException e) {
            return new DependencyResolution(e);
        }
    }

    @Override
    public List<DownloadablePackage> getUninstallDependencies(Package pkg, String targetPlatform) {
        List<DownloadablePackage> packagesToUninstall = new ArrayList<>();
        List<String> removes = new ArrayList<>();
        removes.add(pkg.getName());
        DependencyResolution resolution = resolveDependencies(null, removes, null, targetPlatform);
        if (!resolution.isFailed() && !resolution.isEmpty()) {
            List<String> idsToRemove = resolution.getOrderedPackageIdsToRemove();
            idsToRemove.remove(pkg.getId());
            for (String pkgIdToRemove : idsToRemove) {
                DownloadablePackage localPackage = findPackageById(pkgIdToRemove, localSources);
                if (localPackage != null) {
                    packagesToUninstall.add(localPackage);
                } else {
                    log.error("Missing local package to remove: " + pkgIdToRemove);
                }
            }
        }
        return packagesToUninstall;
    }

    @Deprecated
    @Override
    public List<DownloadablePackage> getUninstallDependencies(Package pkg) {
        // This impl is clearly not very sharp
        List<String> pkgNamesToRemove = new ArrayList<>();
        List<DownloadablePackage> installedPackages = listInstalledPackages();
        int nbImpactedPackages = 0;
        pkgNamesToRemove.add(pkg.getName());
        while (pkgNamesToRemove.size() > nbImpactedPackages) {
            nbImpactedPackages = pkgNamesToRemove.size();
            for (DownloadablePackage p : installedPackages) {
                if (!pkgNamesToRemove.contains(p.getName())) {
                    for (PackageDependency dep : p.getDependencies()) {
                        if (pkgNamesToRemove.contains(dep.getName())) {
                            pkgNamesToRemove.add(p.getName());
                            break;
                        }
                    }
                }
            }
        }
        pkgNamesToRemove.remove(pkg.getName());
        List<DownloadablePackage> packagesToUninstall = new ArrayList<>();
        for (String pkgName : pkgNamesToRemove) {
            for (Version v : findLocalPackageInstalledVersions(pkgName)) {
                DownloadablePackage p = getLocalPackage(pkgName + "-" + v.toString());
                packagesToUninstall.add(p);
            }

        }
        return packagesToUninstall;
    }

    @Override
    public List<DownloadablePackage> listAllPackages() {
        return getAllPackages(getAllSources(), null);
    }

    @Override
    public boolean isInstalled(Package pkg) {
        return isInstalled(pkg.getId());
    }

    @Override
    public boolean isInstalled(String pkgId) {
        DownloadablePackage pkg = getLocalPackage(pkgId);
        return (pkg != null && pkg.getPackageState().isInstalled());
    }

    @Override
    public void order(DependencyResolution res) throws DependencyException {
        Map<String, DownloadablePackage> allPackagesByID = getAllPackagesByID();
        synchronized (res) {
            if (!res.isSorted()) {
                res.sort(this);
            }
            List<String> installOrder = res.getOrderedPackageIdsToInstall();
            List<String> removeOrder = res.getOrderedPackageIdsToRemove();
            orderByDependencies(allPackagesByID, installOrder, removeOrder, false);
            orderByDependencies(allPackagesByID, removeOrder, removeOrder, true);
            Collections.reverse(removeOrder);
        }
    }

    /**
     * Sort the given orderedList list of package ids by dependencies and optional dependencies. If a package A has a
     * dependency on a package B, B will be ordered before A. If B is missing, a {@link DependencyException} will be
     * thrown. If a package C has an optional dependency on a package D, D will be ordered before C. If D is missing, a
     * message will be logged to inform that D will be ignored.
     *
     * @param allPackagesByID all available packages sorted by id
     * @param listToOrder the package ids list to order
     * @param orderedRemoveList the package ids list which are going to be removed
     * @param isRemoveList if true, no message will be logged for missing optional dependencies
     * @throws DependencyException if one ore more dependency (non optional) is missing
     */
    private void orderByDependencies(Map<String, DownloadablePackage> allPackagesByID, List<String> listToOrder,
            List<String> orderedRemoveList, boolean isRemoveList) throws DependencyException {
        Map<String, Package> orderedMap = Collections.synchronizedMap(new LinkedHashMap<String, Package>());
        boolean hasChanged = true;
        Set<String> missingDeps = new HashSet<>();
        Map<String, Set<String>> optionalMissingDeps = new HashMap<>();
        while (!listToOrder.isEmpty() && hasChanged) {
            hasChanged = false;
            for (String id : listToOrder) {
                DownloadablePackage pkg = allPackagesByID.get(id);
                if (pkg.getDependencies().length == 0 && pkg.getOptionalDependencies().length == 0) {
                    // Add pkg to orderedMap if it has no dependencies nor optional dependencies
                    orderedMap.put(id, pkg);
                    hasChanged = true;
                } else {
                    // Add to orderedMap if all its dependencies and optional dependencies are satisfied
                    boolean allSatisfied = true;
                    List<PackageDependency> allDependencies = new ArrayList<>();
                    CollectionUtils.addAll(allDependencies, pkg.getDependencies());
                    List<PackageDependency> optionalDependencies = Arrays.asList(pkg.getOptionalDependencies());
                    allDependencies.addAll(optionalDependencies);
                    for (PackageDependency pkgDep : allDependencies) {
                        // is pkDep optional?
                        boolean isOptionalPkgDep = optionalDependencies.contains(pkgDep);
                        // is pkgDep satisfied in orderedMap?
                        boolean satisfied = false;
                        for (Package orderedPkg : orderedMap.values()) {
                            if (matchDependency(pkgDep, orderedPkg)) {
                                satisfied = true;
                                if (isOptionalPkgDep) {
                                    if (optionalMissingDeps.get(id) != null) {
                                        optionalMissingDeps.get(id).remove(pkgDep.toString());
                                    }
                                } else {
                                    missingDeps.remove(pkgDep.toString());
                                }
                                break;
                            }
                        }
                        // else, is pkgDep satisfied in already installed pkgs and is not going to be removed or
                        // upgraded ?
                        if (!satisfied && !hasMatchInIdList(pkgDep, listToOrder)) {
                            for (Version version : findLocalPackageInstalledVersions(pkgDep.getName())) {
                                if ((isRemoveList
                                        || !hasMatchInIdList(pkgDep.getName(), version, orderedRemoveList))
                                        && pkgDep.getVersionRange().matchVersion(version)) {
                                    satisfied = true;
                                    if (isOptionalPkgDep) {
                                        if (optionalMissingDeps.get(id) != null) {
                                            optionalMissingDeps.get(id).remove(pkgDep.toString());
                                        }
                                    } else {
                                        missingDeps.remove(pkgDep.toString());
                                    }
                                    break;
                                }
                            }
                            // if it's an optional dependency that will not be installed
                            if (!satisfied && isOptionalPkgDep) {
                                // consider the pkDep as satisfied, but add it in optional missing dependencies for
                                // logging if it is not going to be removed
                                satisfied = true;
                                if (!hasMatchInIdList(pkgDep, orderedRemoveList)) {
                                    optionalMissingDeps.computeIfAbsent(id, k -> new HashSet<>())
                                            .add(pkgDep.toString());
                                }
                            }
                        }

                        if (!satisfied) { // couldn't satisfy pkgDep
                            allSatisfied = false;
                            if (isOptionalPkgDep) {
                                optionalMissingDeps.computeIfAbsent(id, k -> new HashSet<>())
                                        .add(pkgDep.toString());
                            } else {
                                missingDeps.add(pkgDep.toString());
                            }
                            break;
                        }
                    }
                    if (allSatisfied) {
                        orderedMap.put(id, pkg);
                        orderedRemoveList.remove(pkg.getName());
                        hasChanged = true;
                    }
                }
            }
            listToOrder.removeAll(orderedMap.keySet());
        }
        if (!optionalMissingDeps.isEmpty() && !isRemoveList) {
            for (Entry<String, Set<String>> entry : optionalMissingDeps.entrySet()) {
                if (entry.getValue() != null && !entry.getValue().isEmpty()) {
                    log.info(String.format("Optional dependencies %s will be ignored for '%s'.", entry.getValue(),
                            entry.getKey()));
                }
            }
        }
        if (!listToOrder.isEmpty()) {
            if (missingDeps.isEmpty()) {
                for (String id : listToOrder) {
                    DownloadablePackage pkg = allPackagesByID.get(id);
                    orderedMap.put(id, pkg);
                }
                listToOrder.clear();
            } else {
                throw new DependencyException(
                        String.format("Couldn't order %s missing %s.", listToOrder, missingDeps));
            }
        }
        listToOrder.addAll(orderedMap.keySet());
    }

    private boolean hasMatchInIdList(String pkgName, Version pkgVersion, List<String> pkgIdList) {
        String pkgId = pkgName + "-" + pkgVersion;
        return pkgIdList.contains(pkgId);
    }

    private boolean hasMatchInIdList(PackageDependency pkgDep, List<String> pkgIdList) {
        for (String pkgId : pkgIdList) {
            Package pkg = getPackage(pkgId);
            if (matchDependency(pkgDep, pkg)) {
                return true;
            }
        }
        return false;
    }

    private boolean matchDependency(PackageDependency pkgDep, Package pkg) {
        boolean match = pkgDep.getName().equals(pkg.getName())
                && pkgDep.getVersionRange().matchVersion(pkg.getVersion());
        if (!match && pkg.getProvides() != null) { // Look at provides
            for (PackageDependency provide : pkg.getProvides()) {
                if (pkgDep.getName().equals(provide.getName())
                        && pkgDep.getVersionRange().matchVersionRange(provide.getVersionRange())) {
                    match = true;
                    break;
                }
            }
        }
        return match;
    }

    @Override
    public void cancelDownload(String pkgId) {
        ConnectDownloadManager cdm = NuxeoConnectClient.getDownloadManager();
        cdm.removeDownloadingPackage(pkgId);
    }

    @Override
    public List<? extends Package> sort(List<? extends Package> pkgs) {
        Collections.sort(pkgs, new PackageComparator());
        return pkgs;
    }

    @Override
    public String getNonCompliant(List<String> packages, String targetPlatform) throws PackageException {
        for (String pkg : packages) {
            if (!matchesPlatform(pkg, targetPlatform)) {
                return pkg;
            }
        }
        return null;
    }

    @Override
    public List<String> getNonCompliantList(List<String> packages, String targetPlatform) throws PackageException {
        List<String> nonCompliant = new ArrayList<>();
        for (String pkg : packages) {
            if (!matchesPlatform(pkg, targetPlatform)) {
                nonCompliant.add(pkg);
            }
        }
        return nonCompliant;
    }

    @Override
    public boolean matchesPlatform(String requestPkgStr, String targetPlatform) throws PackageException {
        Map<String, DownloadablePackage> allPackagesByID = getAllPackagesByID();
        // Try ID match first
        if (allPackagesByID.containsKey(requestPkgStr)) {
            return TargetPlatformFilterHelper.isCompatibleWithTargetPlatform(allPackagesByID.get(requestPkgStr),
                    targetPlatform);
        }
        // Fallback on name match
        List<DownloadablePackage> allPackagesForName = getAllPackagesByName().get(requestPkgStr);
        if (allPackagesForName == null) {
            throw new PackageException("Package not found: " + requestPkgStr);
        }
        for (DownloadablePackage pkg : allPackagesForName) {
            if (requestPkgStr.equals(pkg.getName())
                    && TargetPlatformFilterHelper.isCompatibleWithTargetPlatform(pkg, targetPlatform)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void checkOptionalDependenciesOnInstalledPackages(DependencyResolution res) {
        List<DownloadablePackage> installedPackages = listInstalledPackages();
        synchronized (res) {
            Set<DownloadablePackage> packagesToReinstall = new HashSet<>();
            for (DownloadablePackage installedPkg : installedPackages) {
                if (res.getOrderedPackageIdsToRemove().contains(installedPkg.getId())) {
                    // no check to do on packages being removed
                    continue;
                }
                PackageDependency[] optionalDependencies = installedPkg.getOptionalDependencies();
                for (PackageDependency pkgOptDep : optionalDependencies) {
                    // is pkgOptDep already installed ?
                    List<Version> installedVersions = findLocalPackageInstalledVersions(pkgOptDep.getName());
                    boolean hasAnInstalledMatch = false;
                    for (Version version : installedVersions) {
                        if (pkgOptDep.getVersionRange().matchVersion(version)) {
                            hasAnInstalledMatch = true;
                            break;
                        }
                    }
                    if (!hasAnInstalledMatch) {// if not, is pkgOptDep going to be installed ?
                        for (String pkgId : res.getOrderedPackageIdsToInstall()) {
                            DownloadablePackage pkgToInstall = getPackage(pkgId);
                            if (matchDependency(pkgOptDep, pkgToInstall)) {
                                // if yes, mark installedPkg for reinstall
                                res.addReinstallForNewlyInstalledOptional(installedPkg.getId(),
                                        pkgToInstall.getId());
                                packagesToReinstall.add(installedPkg);
                                break;
                            }
                        }
                    } else { // if yes, is pkgOptDep going to be upgraded ?
                        boolean hasAnUpgradingMatch = hasMatchInIdList(pkgOptDep,
                                res.getOrderedPackageIdsToInstall());
                        if (!hasAnUpgradingMatch) { // if not, is pkgOptDep going to be removed ?
                            for (String pkgId : res.getOrderedPackageIdsToRemove()) {
                                DownloadablePackage pkgToRemove = getPackage(pkgId);
                                if (matchDependency(pkgOptDep, pkgToRemove)) {
                                    // if yes, mark installedPkg for reinstall
                                    res.addReinstallForNewlyRemovedOptional(installedPkg.getId(),
                                            pkgToRemove.getId());
                                    packagesToReinstall.add(installedPkg);
                                    break;
                                }
                            }
                        }
                    }
                }
            }
            for (DownloadablePackage pkg : packagesToReinstall) {
                res.getOrderedPackageIdsToInstall().add(pkg.getId());
                res.markPackageForRemoval(pkg.getName(), pkg.getVersion());
            }
        }
    }

}