blob: 228b517f66844589580b717fc3cf1edbc9badb3f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2008 IBM Corporation and others. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial implementation and ideas
******************************************************************************/
package org.eclipse.equinox.internal.p2.reconciler.dropins;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.Map.Entry;
import org.eclipse.core.runtime.*;
import org.eclipse.equinox.internal.p2.core.helpers.LogHelper;
import org.eclipse.equinox.internal.provisional.configurator.Configurator;
import org.eclipse.equinox.internal.provisional.p2.artifact.repository.IArtifactRepository;
import org.eclipse.equinox.internal.provisional.p2.artifact.repository.IFileArtifactRepository;
import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
import org.eclipse.equinox.internal.provisional.p2.core.repository.IRepository;
import org.eclipse.equinox.internal.provisional.p2.director.*;
import org.eclipse.equinox.internal.provisional.p2.engine.*;
import org.eclipse.equinox.internal.provisional.p2.metadata.*;
import org.eclipse.equinox.internal.provisional.p2.metadata.MetadataFactory.InstallableUnitDescription;
import org.eclipse.equinox.internal.provisional.p2.metadata.query.InstallableUnitQuery;
import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepository;
import org.eclipse.equinox.internal.provisional.p2.query.Collector;
import org.osgi.framework.*;
/**
* Synchronizes a profile with a set of repositories.
*/
public class ProfileSynchronizer {
private static final String TIMESTAMPS_FILE_PREFIX = "timestamps"; //$NON-NLS-1$
private static final String PROFILE_TIMESTAMP = "PROFILE"; //$NON-NLS-1$
private static final String NO_TIMESTAMP = "-1"; //$NON-NLS-1$
private static final String SUPER_IU = "org.eclipse.equinox.p2.dropins"; //$NON-NLS-1$
public class ListCollector extends Collector {
public List getList() {
return super.getList();
}
}
private static final String CACHE_EXTENSIONS = "org.eclipse.equinox.p2.cache.extensions"; //$NON-NLS-1$
private static final String PIPE = "|"; //$NON-NLS-1$
final IProfile profile;
final Map repositoryMap;
private Properties timestamps;
/*
* Constructor for the class.
*/
public ProfileSynchronizer(IProfile profile, Collection repositories) {
this.profile = profile;
this.repositoryMap = new HashMap();
for (Iterator it = repositories.iterator(); it.hasNext();) {
IMetadataRepository repository = (IMetadataRepository) it.next();
repositoryMap.put(repository.getLocation().toExternalForm(), repository);
}
}
/*
* Synchronize the profile with the list of metadata repositories.
*/
public IStatus synchronize(IProgressMonitor monitor) {
readTimestamps();
if (isUpToDate())
return Status.OK_STATUS;
IStatus status = synchronizeCacheExtensions();
if (!status.isOK())
return status;
ProvisioningContext context = getContext();
ProfileChangeRequest request = createProfileChangeRequest(context);
if (request == null)
return Status.OK_STATUS;
SubMonitor sub = SubMonitor.convert(monitor, 100);
try {
//create the provisioning plan
ProvisioningPlan plan = createProvisioningPlan(request, context, sub.newChild(50));
status = plan.getStatus();
if (status.getSeverity() == IStatus.ERROR || plan.getOperands().length == 0)
return status;
//invoke the engine to perform installs/uninstalls
IStatus engineResult = executePlan(plan, context, sub.newChild(50));
if (!engineResult.isOK())
return engineResult;
writeTimestamps();
applyConfiguration();
return status;
} finally {
sub.done();
}
}
private void writeTimestamps() {
timestamps.clear();
timestamps.put(PROFILE_TIMESTAMP, Long.toString(profile.getTimestamp()));
for (Iterator it = repositoryMap.entrySet().iterator(); it.hasNext();) {
Entry entry = (Entry) it.next();
IMetadataRepository repository = (IMetadataRepository) entry.getValue();
Map props = repository.getProperties();
String timestamp = null;
if (props != null)
timestamp = (String) props.get(IRepository.PROP_TIMESTAMP);
if (timestamp == null)
timestamp = NO_TIMESTAMP;
timestamps.put(entry.getKey(), timestamp);
}
try {
File file = Activator.getContext().getDataFile(TIMESTAMPS_FILE_PREFIX + profile.getProfileId().hashCode());
OutputStream os = new BufferedOutputStream(new FileOutputStream(file));
try {
timestamps.save(os, "Timestamps for " + profile.getProfileId()); //$NON-NLS-1$
} finally {
if (os != null)
os.close();
}
} catch (FileNotFoundException e) {
//Ignore
} catch (IOException e) {
//Ignore
}
}
private boolean isUpToDate() {
String lastKnownProfileTimeStamp = (String) timestamps.remove(PROFILE_TIMESTAMP);
if (lastKnownProfileTimeStamp == null)
return false;
if (!lastKnownProfileTimeStamp.equals(Long.toString(profile.getTimestamp())))
return false;
//When we get here the timestamps map only contains information related to repos
for (Iterator it = repositoryMap.entrySet().iterator(); it.hasNext();) {
Entry entry = (Entry) it.next();
IMetadataRepository repository = (IMetadataRepository) entry.getValue();
Map props = repository.getProperties();
String currentTimestamp = null;
if (props != null)
currentTimestamp = (String) props.get(IRepository.PROP_TIMESTAMP);
if (currentTimestamp == null)
currentTimestamp = NO_TIMESTAMP;
String lastKnownTimestamp = (String) timestamps.remove(entry.getKey());
//A repo has been added
if (lastKnownTimestamp == null)
return false;
if (!lastKnownTimestamp.equals(currentTimestamp)) {
return false;
}
}
//A repo has been removed
if (timestamps.size() != 0)
return false;
return true;
}
private void readTimestamps() {
File file = Activator.getContext().getDataFile(TIMESTAMPS_FILE_PREFIX + profile.getProfileId().hashCode());
timestamps = new Properties();
try {
InputStream is = new BufferedInputStream(new FileInputStream(file));
try {
timestamps.load(is);
} finally {
if (is != null)
is.close();
}
} catch (FileNotFoundException e) {
//Ignore
} catch (IOException e) {
//Ignore
}
}
private ProvisioningContext getContext() {
ArrayList repoURLs = new ArrayList();
for (Iterator iterator = repositoryMap.keySet().iterator(); iterator.hasNext();) {
try {
repoURLs.add(new URL((String) iterator.next()));
} catch (MalformedURLException e) {
//ignore
}
}
return new ProvisioningContext((URL[]) repoURLs.toArray(new URL[repoURLs.size()]));
}
private IStatus synchronizeCacheExtensions() {
List currentExtensions = new ArrayList();
StringBuffer buffer = new StringBuffer();
for (Iterator it = repositoryMap.keySet().iterator(); it.hasNext();) {
String repositoryId = (String) it.next();
try {
IArtifactRepository repository = Activator.loadArtifactRepository(new URL(repositoryId));
if (repository instanceof IFileArtifactRepository) {
currentExtensions.add(repositoryId);
buffer.append(repositoryId);
if (it.hasNext())
buffer.append(PIPE);
}
} catch (ProvisionException e) {
// ignore
} catch (MalformedURLException e) {
// unexpected
e.printStackTrace();
}
}
String currentExtensionsProperty = (buffer.length() == 0) ? null : buffer.toString();
List previousExtensions = new ArrayList();
String previousExtensionsProperty = profile.getProperty(CACHE_EXTENSIONS);
if (previousExtensionsProperty != null) {
StringTokenizer tokenizer = new StringTokenizer(previousExtensionsProperty, PIPE);
while (tokenizer.hasMoreTokens()) {
previousExtensions.add(tokenizer.nextToken());
}
}
if (previousExtensions.size() == currentExtensions.size() && previousExtensions.containsAll(currentExtensions))
return Status.OK_STATUS;
Operand operand = new PropertyOperand(CACHE_EXTENSIONS, previousExtensionsProperty, currentExtensionsProperty);
return executeOperands(new ProvisioningContext(new URL[0]), new Operand[] {operand}, null);
}
private IInstallableUnit createRootIU(List children) {
InstallableUnitDescription iu = new MetadataFactory.InstallableUnitDescription();
iu.setId(SUPER_IU);
iu.setVersion(new Version("1.0.0.v" + System.currentTimeMillis()));
List required = new ArrayList();
for (Iterator iter = children.iterator(); iter.hasNext();) {
IInstallableUnit next = (IInstallableUnit) iter.next();
required.add(MetadataFactory.createRequiredCapability(IInstallableUnit.NAMESPACE_IU_ID, next.getId(), null, null, false /* optional */, false, true));
}
if (required.size() > 0)
iu.setRequiredCapabilities((RequiredCapability[]) required.toArray(new RequiredCapability[required.size()]));
return MetadataFactory.createInstallableUnit(iu);
}
private ProfileChangeRequest createProfileChangeRequest(ProvisioningContext context) {
List toAdd = new ArrayList();
List defaults = new ArrayList();
Collector allIUs = getAllIUsFromRepos();
//Nothing has changed
IInstallableUnit previous = getIU(SUPER_IU);
//Empty repo
ProfileChangeRequest request = new ProfileChangeRequest(profile);
if (allIUs.size() == 0) {
if (previous == null)
return null;
//Request the removal of the super IU
request.removeInstallableUnits(new IInstallableUnit[] {previous});
return request;
}
for (Iterator iterator = allIUs.iterator(); iterator.hasNext();) {
IInstallableUnit iu = (IInstallableUnit) iterator.next();
defaults.add(createDefaultIU(iu));
toAdd.add(createIncludedIU(iu));
if (Boolean.valueOf(iu.getProperty(IInstallableUnit.PROP_TYPE_GROUP)).booleanValue())
request.setInstallableUnitProfileProperty(iu, IInstallableUnit.PROP_PROFILE_ROOT_IU, Boolean.TRUE.toString());
}
List extra = new ArrayList();
extra.addAll(defaults);
extra.addAll(toAdd);
context.setExtraIUs(extra);
// only add one IU to the request. it will contain all the other IUs we want to install
IInstallableUnit rootIU = createRootIU(toAdd);
request.addInstallableUnits(new IInstallableUnit[] {rootIU});
//Request the removal of the previous super IU
if (previous != null)
request.removeInstallableUnits(new IInstallableUnit[] {previous});
return request;
}
private Collector getAllIUsFromRepos() {
Collector allRepos = new Collector();
for (Iterator it = repositoryMap.entrySet().iterator(); it.hasNext();) {
Entry entry = (Entry) it.next();
IMetadataRepository repository = (IMetadataRepository) entry.getValue();
repository.query(InstallableUnitQuery.ANY, allRepos, null).iterator();
}
return allRepos;
}
private IInstallableUnit createIncludedIU(IInstallableUnit iu) {
InstallableUnitDescription iud = new MetadataFactory.InstallableUnitDescription();
iud.setId(iu.getId());
iud.setVersion(new Version(0, 0, 0, Long.toString(System.currentTimeMillis())));
RequiredCapability[] reqs = new RequiredCapability[] {MetadataFactory.createRequiredCapability(IInstallableUnit.NAMESPACE_IU_ID, iu.getId(), null, null, false, false, true)};
iud.setRequiredCapabilities(reqs);
return MetadataFactory.createInstallableUnit(iud);
}
private IInstallableUnit createDefaultIU(IInstallableUnit iu) {
InstallableUnitDescription iud = new MetadataFactory.InstallableUnitDescription();
iud.setId(iu.getId());
iud.setVersion(new Version(0, 0, 0));
iud.setCapabilities(new ProvidedCapability[] {MetadataFactory.createProvidedCapability(IInstallableUnit.NAMESPACE_IU_ID, iu.getId(), new Version(0, 0, 0))});
return MetadataFactory.createInstallableUnit(iud);
}
private IInstallableUnit getIU(String iuId) {
ListCollector collector = new ListCollector();
profile.query(new InstallableUnitQuery(iuId), collector, null);
if (collector.size() > 0)
return (IInstallableUnit) collector.iterator().next();
return null;
}
private ProvisioningPlan createProvisioningPlan(ProfileChangeRequest request, ProvisioningContext provisioningContext, IProgressMonitor monitor) {
BundleContext context = Activator.getContext();
ServiceReference reference = context.getServiceReference(IPlanner.class.getName());
IPlanner planner = (IPlanner) context.getService(reference);
try {
return planner.getProvisioningPlan(request, provisioningContext, monitor);
} finally {
context.ungetService(reference);
}
}
private IStatus executePlan(ProvisioningPlan plan, ProvisioningContext provisioningContext, IProgressMonitor monitor) {
Operand[] operands = plan.getOperands();
return executeOperands(provisioningContext, operands, monitor);
}
private IStatus executeOperands(ProvisioningContext provisioningContext, Operand[] operands, IProgressMonitor monitor) {
BundleContext context = Activator.getContext();
ServiceReference reference = context.getServiceReference(IEngine.class.getName());
IEngine engine = (IEngine) context.getService(reference);
try {
PhaseSet phaseSet = new DefaultPhaseSet();
IStatus engineResult = engine.perform(profile, phaseSet, operands, provisioningContext, monitor);
return engineResult;
} finally {
context.ungetService(reference);
}
}
/*
* Write out the configuration file.
*/
private void applyConfiguration() {
BundleContext context = Activator.getContext();
ServiceReference reference = context.getServiceReference(Configurator.class.getName());
Configurator configurator = (Configurator) context.getService(reference);
try {
configurator.applyConfiguration();
} catch (IOException e) {
LogHelper.log(new Status(IStatus.ERROR, Activator.ID, "Unexpected failure applying configuration", e)); //$NON-NLS-1$
} finally {
context.ungetService(reference);
}
}
}