blob: 12b7780fca85352e7446e8fa285490a61791c48e [file] [log] [blame]
/*******************************************************************************
* 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);
}
}
}
}