| /******************************************************************************* |
| * Copyright (c) 2007, 2019 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.equinox.internal.p2.garbagecollector; |
| |
| import java.util.*; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.core.runtime.preferences.*; |
| import org.eclipse.equinox.internal.p2.core.helpers.LogHelper; |
| import org.eclipse.equinox.internal.p2.engine.*; |
| import org.eclipse.equinox.internal.provisional.p2.core.eventbus.IProvisioningEventBus; |
| import org.eclipse.equinox.internal.provisional.p2.core.eventbus.SynchronousProvisioningListener; |
| import org.eclipse.equinox.p2.core.IProvisioningAgent; |
| import org.eclipse.equinox.p2.core.spi.IAgentService; |
| import org.eclipse.equinox.p2.engine.IProfile; |
| import org.eclipse.equinox.p2.engine.IProfileRegistry; |
| import org.eclipse.equinox.p2.metadata.IArtifactKey; |
| import org.eclipse.equinox.p2.repository.artifact.IArtifactRepository; |
| import org.osgi.service.prefs.Preferences; |
| |
| /** |
| * The main control point for the p2 garbage collector. Takes a Profile and runs the CoreGarbageCollector with the |
| * appropriate MarkSets for the repositories used by that Profile. |
| * |
| * Takes the profile passed in and creates a set (markSet) that maps the artifact repositories it uses to the |
| * artifact keys its IUs hold. This is done by getting MarkSets from all registered IMarkSetProviders. |
| * |
| * Then, the MarkSets are obtained for every other registered Profile in a similar fashion. Each MarkSet is |
| * checked to see if its artifact repository is already a key in markSet. If so, that MarkSet's artifact keys |
| * are added to the list that is mapped to by the artifact repository. |
| */ |
| public class GarbageCollector implements SynchronousProvisioningListener, IAgentService { |
| /** |
| * Service name constant for the garbage collection service. |
| */ |
| public static final String SERVICE_NAME = GarbageCollector.class.getName(); |
| |
| private class ParameterizedSafeRunnable implements ISafeRunnable { |
| IProfile aProfile; |
| MarkSet[] aProfileMarkSets; |
| IConfigurationElement cfg; |
| |
| public ParameterizedSafeRunnable(IConfigurationElement runtAttribute, IProfile profile) { |
| cfg = runtAttribute; |
| aProfile = profile; |
| } |
| |
| public MarkSet[] getResult() { |
| return aProfileMarkSets; |
| } |
| |
| @Override |
| public void handleException(Throwable exception) { |
| LogHelper.log(new Status(IStatus.ERROR, GarbageCollectorHelper.ID, Messages.Error_in_extension, exception)); |
| } |
| |
| @Override |
| public void run() throws Exception { |
| MarkSetProvider aMarkSetProvider = (MarkSetProvider) cfg.createExecutableExtension(ATTRIBUTE_CLASS); |
| if (aMarkSetProvider == null) { |
| aProfileMarkSets = null; |
| return; |
| } |
| aProfileMarkSets = aMarkSetProvider.getMarkSets(agent, aProfile); |
| } |
| } |
| |
| private static final String ATTRIBUTE_CLASS = "class"; //$NON-NLS-1$ |
| |
| private static final String PT_MARKSET = GarbageCollectorHelper.ID + ".marksetproviders"; //$NON-NLS-1$ |
| final IProvisioningAgent agent; |
| |
| //The GC is triggered when an uninstall event occurred during a "transaction" and the transaction is committed. |
| String uninstallEventProfileId = null; |
| |
| /** |
| * Maps IArtifactRepository objects to their respective "marked set" of IArtifactKeys |
| */ |
| private Map<IArtifactRepository, Collection<IArtifactKey>> markSet; |
| |
| public GarbageCollector(IProvisioningAgent agent) { |
| this.agent = agent; |
| } |
| |
| private void addKeys(Collection<IArtifactKey> keyList, IArtifactKey[] keyArray) { |
| for (IArtifactKey element : keyArray) |
| keyList.add(element); |
| } |
| |
| private void contributeMarkSets(IConfigurationElement runAttribute, IProfile profile, boolean addRepositories) { |
| ParameterizedSafeRunnable providerExecutor = new ParameterizedSafeRunnable(runAttribute, profile); |
| SafeRunner.run(providerExecutor); |
| MarkSet[] aProfileMarkSets = providerExecutor.getResult(); |
| if (aProfileMarkSets == null || aProfileMarkSets.length == 0 || aProfileMarkSets[0] == null) |
| return; |
| |
| for (MarkSet aProfileMarkSet : aProfileMarkSets) { |
| if (aProfileMarkSet == null) { |
| continue; |
| } |
| Collection<IArtifactKey> keys = markSet.get(aProfileMarkSet.getRepo()); |
| if (keys == null) { |
| if (addRepositories) { |
| keys = new HashSet<>(); |
| markSet.put(aProfileMarkSet.getRepo(), keys); |
| addKeys(keys, aProfileMarkSet.getKeys()); |
| } |
| } else { |
| addKeys(keys, aProfileMarkSet.getKeys()); |
| } |
| } |
| } |
| |
| protected boolean getBooleanPreference(String key, boolean defaultValue) { |
| IPreferencesService prefService = GarbageCollectorHelper.getService(IPreferencesService.class); |
| if (prefService == null) |
| return defaultValue; |
| List<IEclipsePreferences> nodes = new ArrayList<>(); |
| // todo we should look in the instance scope as well but have to be careful that the instance location has been set |
| nodes.add(ConfigurationScope.INSTANCE.getNode(GarbageCollectorHelper.ID)); |
| nodes.add(DefaultScope.INSTANCE.getNode(GarbageCollectorHelper.ID)); |
| return Boolean.parseBoolean(prefService.get(key, Boolean.toString(defaultValue), nodes.toArray(new Preferences[nodes.size()]))); |
| } |
| |
| private void invokeCoreGC() { |
| for (IArtifactRepository nextRepo : markSet.keySet()) { |
| IArtifactKey[] keys = markSet.get(nextRepo).toArray(new IArtifactKey[0]); |
| MarkSet aMarkSet = new MarkSet(keys, nextRepo); |
| new CoreGarbageCollector().clean(aMarkSet.getKeys(), aMarkSet.getRepo()); |
| } |
| } |
| |
| @Override |
| public void notify(EventObject o) { |
| if (o instanceof InstallableUnitEvent) { |
| InstallableUnitEvent event = (InstallableUnitEvent) o; |
| if (event.isUninstall() && event.isPost()) { |
| uninstallEventProfileId = event.getProfile().getProfileId(); |
| } |
| } else if (o instanceof CommitOperationEvent) { |
| if (uninstallEventProfileId != null) { |
| CommitOperationEvent event = (CommitOperationEvent) o; |
| if (uninstallEventProfileId.equals(event.getProfile().getProfileId()) && getBooleanPreference(GarbageCollectorHelper.GC_ENABLED, true)) |
| runGC(event.getProfile()); |
| uninstallEventProfileId = null; |
| } |
| } else if (o instanceof RollbackOperationEvent) { |
| if (uninstallEventProfileId != null && uninstallEventProfileId.equals(((RollbackOperationEvent) o).getProfile().getProfileId())) |
| uninstallEventProfileId = null; |
| } |
| } |
| |
| public void runGC(IProfile profile) { |
| markSet = new HashMap<>(); |
| if (!traverseMainProfile(profile)) |
| return; |
| |
| //Complete each MarkSet with the MarkSets provided by all of the other registered Profiles |
| traverseRegisteredProfiles(); |
| |
| //Run the GC on each MarkSet |
| invokeCoreGC(); |
| } |
| |
| @Override |
| public void start() { |
| IProvisioningEventBus eventBus = agent.getService(IProvisioningEventBus.class); |
| if (eventBus == null) |
| return; |
| eventBus.addListener(this); |
| } |
| |
| @Override |
| public void stop() { |
| IProvisioningEventBus eventBus = agent.getService(IProvisioningEventBus.class); |
| if (eventBus != null) |
| eventBus.removeListener(this); |
| } |
| |
| private boolean traverseMainProfile(IProfile profile) { |
| IExtensionRegistry registry = RegistryFactory.getRegistry(); |
| IConfigurationElement[] configElts = registry.getConfigurationElementsFor(PT_MARKSET); |
| |
| //First we collect all repos and keys for the profile being GC'ed |
| for (IConfigurationElement configElt : configElts) { |
| if (configElt == null || !(configElt.getName().equals("run"))) { //$NON-NLS-1$ |
| continue; |
| } |
| contributeMarkSets(configElt, profile, true); |
| } |
| return true; |
| } |
| |
| private void traverseRegisteredProfiles() { |
| IExtensionRegistry registry = RegistryFactory.getRegistry(); |
| IConfigurationElement[] configElts = registry.getConfigurationElementsFor(PT_MARKSET); |
| for (IConfigurationElement configElt : configElts) { |
| if (configElt == null || !(configElt.getName().equals("run"))) { //$NON-NLS-1$ |
| continue; |
| } |
| IProfileRegistry profileRegistry = agent.getService(IProfileRegistry.class); |
| if (profileRegistry == null) |
| return; |
| IProfile[] registeredProfiles = profileRegistry.getProfiles(); |
| for (IProfile registeredProfile : registeredProfiles) { |
| contributeMarkSets(configElt, registeredProfile, false); |
| } |
| } |
| } |
| } |