package pl.net.bluesoft.rnd.processtool.plugins.osgi.newfelix; import org.osgi.framework.Bundle; import java.util.*; import java.util.logging.Logger; import static pl.net.bluesoft.util.lang.cquery.CQuery.from; /** * User: POlszewski * Date: 2012-11-27 * Time: 20:33 * * This implementation is intended to replace original one. Needs extensive testing. */ public class DependencyWiseBundleInstaller extends BundleInstaller { private Set<String> bundlePathsToRemove; private Set<String> bundlePathsToInstall; public DependencyWiseBundleInstaller(NewFelixBundleService felixService, BundleInfo bundleInfo, Logger logger) { super(felixService, bundleInfo, logger); } public void syncBundlesWithRepository() { getBundleInfo().installationStarted(); getBundleInfo().updateBundleRepositoryStatus(); determineAffectedBundles(); removeBundles(); if (!bundlePathsToInstall.isEmpty()) { installBundles(); } getBundleInfo().installationFinished(); } private void determineAffectedBundles() { List<String> installedBundlePaths = getFelixService().getInstalledBundlePaths(); Collection<String> uninstalled = from(installedBundlePaths).except(getBundleInfo().getExistingBundlePaths()); Collection<String> toReinstall = from(installedBundlePaths).intersect(getBundleInfo().getInstallableBundlePaths()); bundlePathsToRemove = from(uninstalled).union(toReinstall).toSet(); bundlePathsToRemove = getBundleInfo().getInverseDependencyClosure(bundlePathsToRemove); bundlePathsToInstall = from(getBundleInfo().getInstallableBundlePaths()).toSet(); bundlePathsToInstall = from(bundlePathsToInstall).union(getBundleInfo().getInverseDependencyClosure(toReinstall)).toSet(); } private void installBundles() { getBundleInfo().updateDependencyInfo(); List<BundleCommand> commands = new InstallBundleCommandFactory().createCommands(); executeCommands(commands); } private void removeBundles() { List<BundleCommand> commands = new UninstallBundleCommandFactory().createCommands(); executeCommands(commands); } private abstract class BundleCommand { protected final List<String> bundlesToBeInstalled = new ArrayList<String>(); protected final List<String> bundlesToBeUninstalled = new ArrayList<String>(); protected final Set<String> executedWith = new HashSet<String>(); protected final String bundlePath; protected BundleCommand(String bundlePath) { this.bundlePath = bundlePath; } public boolean invoke() { return meetsConditions() && doInvoke(); } protected abstract boolean doInvoke(); public void addBundlesToBeInstalled(Collection<String> bundlePaths) { bundlesToBeInstalled.addAll(bundlePaths); } public void addBundlesToBeUninstalled(Collection<String> bundlePaths) { bundlesToBeUninstalled.addAll(bundlePaths); } public void addExecutedWith(Collection<String> bundlePaths) { executedWith.addAll(bundlePaths); } protected boolean meetsConditions() { for (String bundlePath : bundlesToBeInstalled) { if (getBundleInfo().getBundleStatus(bundlePath) != BundleInfo.Status.INSTALLATION_SUCCEEDED) { return false; } } for (String bundlePath : bundlesToBeUninstalled) { if (getBundleInfo().getBundleStatus(bundlePath) != BundleInfo.Status.UNINSTALLATION_SUCCEEDED) { return false; } } return true; } public boolean retryLater() { return false; } public abstract Collection<String> getDependenciesToBeReached(); public String getBundlePath() { return bundlePath; } @Override public String toString() { return getBundlePath(); } } private class InstallBundleCommand extends BundleCommand { public InstallBundleCommand(String bundlePath) { super(bundlePath); } @Override protected boolean doInvoke() { return getFelixService().doInstallBundle(bundlePath); } @Override public Collection<String> getDependenciesToBeReached() { return from(bundlesToBeInstalled).intersect(executedWith); } } private class UninstallBundleCommand extends BundleCommand { public UninstallBundleCommand(String bundlePath) { super(bundlePath); } @Override protected boolean doInvoke() { Bundle bundle = getFelixService().getBundle(bundlePath); return bundle == null || getFelixService().doUninstallBundle(bundle); } @Override public Collection<String> getDependenciesToBeReached() { return from(bundlesToBeUninstalled).intersect(executedWith); } } private <T extends BundleCommand> void executeCommands(List<T> commands) { while (!commands.isEmpty()) { T command = commands.remove(0); if (!command.invoke()) { if (command.retryLater() && !commands.isEmpty()) { commands.add(1, command); } } } } private abstract class BundleCommandFactory { public List<BundleCommand> createCommands() { Collection<String> bundlePaths = getAffectedBundlePaths(); List<BundleCommand> commands = new ArrayList<BundleCommand>(); for (String bundlePath : bundlePaths) { commands.add(createCommand(bundlePath)); } return determineExecutionOrder(commands); } protected abstract BundleCommand createCommand(String bundlePath); protected abstract Collection<String> getAffectedBundlePaths(); private List<BundleCommand> determineExecutionOrder(List<BundleCommand> commands) { List<BundleCommand> result = new ArrayList<BundleCommand>(); if (commands.isEmpty()) { return result; } Set<String> reachedDependencies = new HashSet<String>(); // plugins that have no dependencies to be installed for (BundleCommand command : commands) { if (command.getDependenciesToBeReached().isEmpty()) { result.add(command); reachedDependencies.add(command.getBundlePath()); } } Set<BundleCommand> toSort = new HashSet<BundleCommand>(commands); toSort.removeAll(result); while (!toSort.isEmpty()) { boolean loaded = false; for (BundleCommand dep : toSort) { if (reachedDependencies.containsAll(dep.getDependenciesToBeReached())) { result.add(dep); reachedDependencies.add(dep.getBundlePath()); toSort.remove(dep); loaded = true; break; } } if (!loaded) { break; } } // remaining plugins to be installed in the end for (BundleCommand dep : toSort) { getLogger().severe(dep.getBundlePath() + " may not be installed because of failed dependency resolution"); } result.addAll(toSort); return result; } } private class InstallBundleCommandFactory extends BundleCommandFactory { @Override protected BundleCommand createCommand(String bundlePath) { InstallBundleCommand command = new InstallBundleCommand(bundlePath); command.addBundlesToBeInstalled(getBundleInfo().getBundleDependencies(bundlePath)); command.addExecutedWith(getAffectedBundlePaths()); return command; } @Override protected Collection<String> getAffectedBundlePaths() { return bundlePathsToInstall; } } private class UninstallBundleCommandFactory extends BundleCommandFactory { @Override protected BundleCommand createCommand(String bundlePath) { UninstallBundleCommand command = new UninstallBundleCommand(bundlePath); command.addBundlesToBeUninstalled(getBundleInfo().getInverseBundleDependencies(bundlePath)); command.addExecutedWith(getAffectedBundlePaths()); return command; } @Override protected Collection<String> getAffectedBundlePaths() { return bundlePathsToRemove; } } }