blob: 888ebdfcf6688a4ab4bcca58c8991ded32b46eff [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 API and implementation
*******************************************************************************/
package org.eclipse.equinox.internal.p2.metadata.repository;
import java.lang.ref.SoftReference;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.preferences.ConfigurationScope;
import org.eclipse.equinox.internal.p2.core.helpers.*;
import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
import org.eclipse.equinox.internal.provisional.p2.core.eventbus.IProvisioningEventBus;
import org.eclipse.equinox.internal.provisional.p2.core.eventbus.ProvisioningListener;
import org.eclipse.equinox.internal.provisional.p2.core.repository.IRepository;
import org.eclipse.equinox.internal.provisional.p2.core.repository.RepositoryEvent;
import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepository;
import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepositoryManager;
import org.eclipse.equinox.internal.provisional.p2.query.Collector;
import org.eclipse.equinox.internal.provisional.p2.query.Query;
import org.eclipse.equinox.internal.provisional.spi.p2.metadata.repository.IMetadataRepositoryFactory;
import org.eclipse.osgi.util.NLS;
import org.osgi.service.prefs.BackingStoreException;
import org.osgi.service.prefs.Preferences;
/**
* Default implementation of {@link IMetadataRepositoryManager}.
*/
public class MetadataRepositoryManager implements IMetadataRepositoryManager, ProvisioningListener {
static class RepositoryInfo {
String description;
boolean isSystem = false;
URL location;
String name;
SoftReference repository;
}
private static final String ATTR_SUFFIX = "suffix"; //$NON-NLS-1$
private static final String DEFAULT_SUFFIX = "content.xml"; //$NON-NLS-1$
private static final String EL_FILTER = "filter"; //$NON-NLS-1$
private static final String FACTORY = "factory"; //$NON-NLS-1$
private static final String KEY_DESCRIPTION = "description"; //$NON-NLS-1$
private static final String KEY_NAME = "name"; //$NON-NLS-1$
private static final String KEY_PROVIDER = "provider"; //$NON-NLS-1$
private static final String KEY_SYSTEM = "isSystem"; //$NON-NLS-1$
private static final String KEY_TYPE = "type"; //$NON-NLS-1$
private static final String KEY_URL = "url"; //$NON-NLS-1$
private static final String KEY_VERSION = "version"; //$NON-NLS-1$
private static final String NODE_REPOSITORIES = "repositories"; //$NON-NLS-1$
/**
* Map of String->RepositoryInfo, where String is the repository key
* obtained via getKey(URL).
*/
private Map repositories = null;
//lock object to be held when referring to the repositories field
private final Object repositoryLock = new Object();
/**
* Cache List of repositories that are not reachable. Maintain cache
* for short duration because repository may become available at any time.
*/
private SoftReference unavailableRepositories;
public MetadataRepositoryManager() {
IProvisioningEventBus bus = (IProvisioningEventBus) ServiceHelper.getService(Activator.getContext(), IProvisioningEventBus.class.getName());
if (bus != null)
bus.addListener(this);
//initialize repositories lazily
}
public void addRepository(IMetadataRepository repository) {
RepositoryInfo info = new RepositoryInfo();
info.repository = new SoftReference(repository);
info.name = repository.getName();
info.description = repository.getDescription();
info.location = repository.getLocation();
String value = (String) repository.getProperties().get(IRepository.PROP_SYSTEM);
info.isSystem = value == null ? false : Boolean.valueOf(value).booleanValue();
boolean added = true;
synchronized (repositoryLock) {
if (repositories == null)
restoreRepositories();
added = repositories.put(getKey(repository), info) == null;
}
// save the given repository in the preferences.
remember(repository);
if (added)
broadcastChangeEvent(repository.getLocation(), IRepository.TYPE_METADATA, RepositoryEvent.ADDED);
}
public void addRepository(URL location) {
Assert.isNotNull(location);
RepositoryInfo info = new RepositoryInfo();
info.location = location;
boolean added = true;
synchronized (repositoryLock) {
if (repositories == null)
restoreRepositories();
added = repositories.put(getKey(location), info) == null;
}
// save the given repository in the preferences.
remember(info);
if (added)
broadcastChangeEvent(location, IRepository.TYPE_METADATA, RepositoryEvent.ADDED);
}
/**
* TODO Eliminate duplication with ArtifactRepositoryManager.
*/
protected void broadcastChangeEvent(URL location, int repositoryType, int kind) {
IProvisioningEventBus bus = (IProvisioningEventBus) ServiceHelper.getService(Activator.getContext(), IProvisioningEventBus.class.getName());
if (bus != null)
bus.publishEvent(new RepositoryEvent(location, repositoryType, kind));
}
/**
* Check if we recently attempted to load the given location and failed
* to find anything. Returns <code>true</code> if the repository was not
* found, and <code>false</code> otherwise.
*/
private boolean checkNotFound(URL location) {
if (unavailableRepositories == null)
return false;
List badRepos = (List) unavailableRepositories.get();
if (badRepos == null)
return false;
return badRepos.contains(location);
}
/**
* Clear the fact that we tried to load a repository at this location and did not find anything.
*/
private void clearNotFound(URL location) {
List badRepos;
if (unavailableRepositories != null) {
badRepos = (List) unavailableRepositories.get();
if (badRepos != null) {
badRepos.remove(location);
return;
}
}
}
/**
* Returns the executable extension, or <code>null</code> if there
* was no corresponding extension, or an error occurred loading it
*/
private Object createExecutableExtension(IExtension extension, String element) {
IConfigurationElement[] elements = extension.getConfigurationElements();
CoreException failure = null;
for (int i = 0; i < elements.length; i++) {
if (elements[i].getName().equals(element)) {
try {
return elements[i].createExecutableExtension("class"); //$NON-NLS-1$
} catch (CoreException e) {
log("Error loading repository extension: " + extension.getUniqueIdentifier(), failure); //$NON-NLS-1$
return null;
}
}
}
log("Malformed repository extension: " + extension.getUniqueIdentifier(), null); //$NON-NLS-1$
return null;
}
/* (non-Javadoc)
* @see org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepositoryManager#createRepository(java.net.URL, java.lang.String, java.lang.String)
*/
public IMetadataRepository createRepository(URL location, String name, String type) throws ProvisionException {
Assert.isNotNull(location);
Assert.isNotNull(name);
Assert.isNotNull(type);
try {
//repository should not already exist
loadRepository(location, (IProgressMonitor) null);
fail(location, ProvisionException.REPOSITORY_EXISTS);
} catch (ProvisionException e) {
//expected - fall through and create the new repository
}
IExtension extension = RegistryFactory.getRegistry().getExtension(Activator.REPO_PROVIDER_XPT, type);
if (extension == null)
fail(location, ProvisionException.REPOSITORY_UNKNOWN_TYPE);
IMetadataRepositoryFactory factory = (IMetadataRepositoryFactory) createExecutableExtension(extension, FACTORY);
if (factory == null)
fail(location, ProvisionException.REPOSITORY_FAILED_READ);
IMetadataRepository result = factory.create(location, name, type);
if (result == null)
fail(location, ProvisionException.REPOSITORY_FAILED_READ);
clearNotFound(location);
return result;
}
private void fail(URL location, int code) throws ProvisionException {
throw new ProvisionException(failStatus(location, code));
}
private IStatus failStatus(URL location, int code) {
String msg = null;
switch (code) {
case ProvisionException.REPOSITORY_EXISTS :
msg = NLS.bind(Messages.repoMan_exists, location);
break;
case ProvisionException.REPOSITORY_UNKNOWN_TYPE :
msg = NLS.bind(Messages.repoMan_unknownType, location);
break;
case ProvisionException.REPOSITORY_FAILED_READ :
msg = NLS.bind(Messages.repoMan_failedRead, location);
break;
case ProvisionException.REPOSITORY_NOT_FOUND :
msg = NLS.bind(Messages.repoMan_notExists, location);
break;
case ProvisionException.REPOSITORY_INVALID_LOCATION :
msg = NLS.bind(Messages.repoMan_invalidLocation, location);
break;
}
if (msg == null)
msg = Messages.repoMan_internalError;
return new Status(IStatus.ERROR, Activator.ID, code, msg, null);
}
private IExtension[] findMatchingRepositoryExtensions(String suffix) {
IConfigurationElement[] elt = RegistryFactory.getRegistry().getConfigurationElementsFor(Activator.REPO_PROVIDER_XPT);
int count = 0;
for (int i = 0; i < elt.length; i++) {
if (elt[i].getName().equals("filter")) { //$NON-NLS-1$
if (!elt[i].getAttribute("suffix").equals(suffix)) { //$NON-NLS-1$
elt[i] = null;
} else {
count++;
}
} else {
elt[i] = null;
}
}
IExtension[] results = new IExtension[count];
for (int i = 0; i < elt.length; i++) {
if (elt[i] != null)
results[--count] = elt[i].getDeclaringExtension();
}
return results;
}
private String[] getAllSuffixes() {
IConfigurationElement[] elements = RegistryFactory.getRegistry().getConfigurationElementsFor(Activator.REPO_PROVIDER_XPT);
ArrayList result = new ArrayList(elements.length);
result.add(DEFAULT_SUFFIX);
for (int i = 0; i < elements.length; i++) {
if (elements[i].getName().equals(EL_FILTER)) {
String suffix = elements[i].getAttribute(ATTR_SUFFIX);
if (!result.contains(suffix))
result.add(suffix);
}
}
return (String[]) result.toArray(new String[result.size()]);
}
/*
* Return a string key based on the given repository which
* is suitable for use as a preference node name.
*/
private String getKey(IMetadataRepository repository) {
return getKey(repository.getLocation());
}
/*
* Return a string key based on the given repository location which
* is suitable for use as a preference node name.
*/
private String getKey(URL location) {
return location.toExternalForm().replace('/', '_');
}
public URL[] getKnownRepositories(int flags) {
synchronized (repositoryLock) {
if (repositories == null)
restoreRepositories();
ArrayList result = new ArrayList();
int i = 0;
for (Iterator it = repositories.values().iterator(); it.hasNext(); i++) {
RepositoryInfo info = (RepositoryInfo) it.next();
if (matchesFlags(info, flags))
result.add(info.location);
}
return (URL[]) result.toArray(new URL[result.size()]);
}
}
/*
* Return the preference node which is the root for where we store the repository information.
*/
private Preferences getPreferences() {
return new ConfigurationScope().getNode(Activator.ID).node(NODE_REPOSITORIES);
}
public IMetadataRepository getRepository(URL location) {
synchronized (repositoryLock) {
if (repositories == null)
restoreRepositories();
for (Iterator it = repositories.values().iterator(); it.hasNext();) {
RepositoryInfo info = (RepositoryInfo) it.next();
if (URLUtil.sameURL(info.location, location)) {
if (info.repository == null)
return null;
IMetadataRepository repo = (IMetadataRepository) info.repository.get();
//update our repository info because the repository may have changed
if (repo != null)
addRepository(repo);
return repo;
}
}
return null;
}
}
/*
* (non-Javadoc)
* @see org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepositoryManager#getRepositoryProperty(java.net.URL, java.lang.String)
*/
public String getRepositoryProperty(URL location, String key) {
synchronized (repositoryLock) {
if (repositories == null)
restoreRepositories();
for (Iterator it = repositories.values().iterator(); it.hasNext();) {
RepositoryInfo info = (RepositoryInfo) it.next();
if (URLUtil.sameURL(info.location, location)) {
if (IRepository.PROP_DESCRIPTION.equals(key))
return info.description;
if (IRepository.PROP_NAME.equals(key))
return info.name;
// Key not known, return null
return null;
}
}
// Repository not found, return null
return null;
}
}
public IMetadataRepository loadRepository(URL location, IProgressMonitor monitor) throws ProvisionException {
Assert.isNotNull(location);
IMetadataRepository result = getRepository(location);
if (result != null)
return result;
MultiStatus notFoundStatus = new MultiStatus(Activator.ID, ProvisionException.REPOSITORY_NOT_FOUND, NLS.bind(Messages.repoMan_notExists, location.toExternalForm()), null);
if (checkNotFound(location))
throw new ProvisionException(notFoundStatus);
String[] suffixes = getAllSuffixes();
SubMonitor sub = SubMonitor.convert(monitor, Messages.REPOMGR_ADDING_REPO, suffixes.length * 100);
try {
for (int i = 0; i < suffixes.length; i++) {
result = loadRepository(location, suffixes[i], sub.newChild(100), notFoundStatus);
if (result != null) {
addRepository(result);
return result;
}
}
} finally {
sub.done();
}
rememberNotFound(location);
throw new ProvisionException(notFoundStatus);
}
/**
* Try to load a pre-existing repo at the given location
*/
private IMetadataRepository loadRepository(URL location, String suffix, SubMonitor monitor, MultiStatus failures) {
IExtension[] providers = findMatchingRepositoryExtensions(suffix);
// Loop over the candidates and return the first one that successfully loads
monitor.beginTask("", providers.length * 10); //$NON-NLS-1$
for (int i = 0; i < providers.length; i++) {
IMetadataRepositoryFactory factory = (IMetadataRepositoryFactory) createExecutableExtension(providers[i], FACTORY);
try {
if (factory != null)
return factory.load(location, monitor.newChild(10));
} catch (ProvisionException e) {
if (e.getStatus().getCode() != ProvisionException.REPOSITORY_NOT_FOUND)
failures.add(e.getStatus());
//keep trying with other factories
}
}
return null;
}
protected void log(String message, Throwable t) {
LogHelper.log(new Status(IStatus.ERROR, Activator.ID, message, t));
}
private boolean matchesFlags(RepositoryInfo info, int flags) {
if ((flags & REPOSITORIES_SYSTEM) == REPOSITORIES_SYSTEM)
if (!info.isSystem)
return false;
if ((flags & REPOSITORIES_NON_SYSTEM) == REPOSITORIES_NON_SYSTEM)
if (info.isSystem)
return false;
if ((flags & REPOSITORIES_LOCAL) == REPOSITORIES_LOCAL)
return "file".equals(info.location.getProtocol()); //$NON-NLS-1$
return true;
}
/*(non-Javadoc)
* @see org.eclipse.equinox.internal.provisional.p2.core.eventbus.ProvisioningListener#notify(java.util.EventObject)
*/
public void notify(EventObject o) {
if (o instanceof RepositoryEvent) {
RepositoryEvent event = (RepositoryEvent) o;
if (event.getKind() == RepositoryEvent.DISCOVERED && event.getRepositoryType() == IRepository.TYPE_METADATA)
addRepository(event.getRepositoryLocation());
}
}
/**
* Sets a preference and returns <code>true</code> if the preference
* was actually changed.
*/
private boolean putValue(Preferences node, String key, String newValue) {
String oldValue = node.get(key, null);
if (oldValue == newValue || (oldValue != null && oldValue.equals(newValue)))
return false;
if (newValue == null)
node.remove(key);
else
node.put(key, newValue);
return true;
}
/**
* Performs a query against all of the installable units of each known
* repository, accumulating any objects that satisfy the query in the
* provided collector.
* <p>
* Note that using this method can be quite expensive, as every known
* metadata repository will be loaded in order to query each one. If a
* client wishes to query only certain repositories, it is better to use
* {@link #getKnownRepositories(int)} to filter the list of repositories
* loaded and then query each of the returned repositories.
* <p>
* This method is long-running; progress and cancellation are provided
* by the given progress monitor.
*
* @param query The query to perform against each installable unit in each known repository
* @param collector Collects the results of the query
* @param monitor a progress monitor, or <code>null</code> if progress
* reporting is not desired
* @return The collector argument
*/
public Collector query(Query query, Collector collector, IProgressMonitor monitor) {
URL[] locations = getKnownRepositories(REPOSITORIES_ALL);
SubMonitor sub = SubMonitor.convert(monitor, locations.length * 10);
for (int i = 0; i < locations.length; i++) {
try {
loadRepository(locations[i], sub.newChild(9)).query(query, collector, sub.newChild(1));
} catch (ProvisionException e) {
//ignore this repository for this query
}
}
sub.done();
return collector;
}
public IMetadataRepository refreshRepository(URL location, IProgressMonitor monitor) throws ProvisionException {
clearNotFound(location);
if (!removeRepository(location))
fail(location, ProvisionException.REPOSITORY_NOT_FOUND);
return loadRepository(location, monitor);
}
/*
* Save the list of repositories in the preference store.
*/
private boolean remember(IMetadataRepository repository) {
boolean changed = false;
Preferences node = getPreferences().node(getKey(repository));
changed |= putValue(node, KEY_URL, repository.getLocation().toExternalForm());
changed |= putValue(node, KEY_DESCRIPTION, repository.getDescription());
changed |= putValue(node, KEY_NAME, repository.getName());
changed |= putValue(node, KEY_PROVIDER, repository.getProvider());
changed |= putValue(node, KEY_TYPE, repository.getType());
changed |= putValue(node, KEY_VERSION, repository.getVersion());
changed |= putValue(node, KEY_SYSTEM, (String) repository.getProperties().get(IRepository.PROP_SYSTEM));
if (changed)
saveToPreferences();
return changed;
}
/*
* Save the list of repositories in the preference store.
*/
private boolean remember(RepositoryInfo info) {
boolean changed = false;
Preferences node = getPreferences().node(getKey(info.location));
changed |= putValue(node, KEY_URL, info.location.toExternalForm());
changed |= putValue(node, KEY_SYSTEM, Boolean.toString(info.isSystem));
changed |= putValue(node, KEY_DESCRIPTION, info.description);
changed |= putValue(node, KEY_NAME, info.name);
if (changed)
saveToPreferences();
return changed;
}
/**
* Cache the fact that we tried to load a repository at this location and did not find anything.
*/
private void rememberNotFound(URL location) {
List badRepos;
if (unavailableRepositories != null) {
badRepos = (List) unavailableRepositories.get();
if (badRepos != null) {
badRepos.add(location);
return;
}
}
badRepos = new ArrayList();
badRepos.add(location);
unavailableRepositories = new SoftReference(badRepos);
}
public boolean removeRepository(URL toRemove) {
Assert.isNotNull(toRemove);
final String repoKey = getKey(toRemove);
synchronized (repositoryLock) {
if (repositories == null)
restoreRepositories();
if (repositories.remove(repoKey) == null)
return false;
}
// remove the repository from the preference store
try {
getPreferences().node(repoKey).removeNode();
saveToPreferences();
} catch (BackingStoreException e) {
log("Error saving preferences", e); //$NON-NLS-1$
}
broadcastChangeEvent(toRemove, IRepository.TYPE_METADATA, RepositoryEvent.REMOVED);
return true;
}
/**
* Restore the list of repositories from the preference store.
*/
private void restoreFromPreferences() {
// restore the list of repositories from the preference store
Preferences node = getPreferences();
String[] children;
try {
children = node.childrenNames();
} catch (BackingStoreException e) {
log("Error restoring repositories from preferences", e); //$NON-NLS-1$
return;
}
for (int i = 0; i < children.length; i++) {
Preferences child = node.node(children[i]);
String locationString = child.get(KEY_URL, null);
if (locationString == null)
continue;
try {
RepositoryInfo info = new RepositoryInfo();
info.location = new URL(locationString);
info.name = child.get(KEY_NAME, null);
info.description = child.get(KEY_DESCRIPTION, null);
info.isSystem = child.getBoolean(KEY_SYSTEM, false);
repositories.put(getKey(info.location), info);
} catch (MalformedURLException e) {
log("Error while restoring repository: " + locationString, e); //$NON-NLS-1$
}
}
}
/**
* Restores metadata repositories specified as system properties.
*/
private void restoreFromSystemProperty() {
String locationString = Activator.getContext().getProperty("eclipse.p2.metadataRepository"); //$NON-NLS-1$
if (locationString == null)
return;
StringTokenizer tokenizer = new StringTokenizer(locationString, ","); //$NON-NLS-1$
while (tokenizer.hasMoreTokens()) {
String pathString = tokenizer.nextToken();
try {
RepositoryInfo info = new RepositoryInfo();
info.location = new URL(pathString);
repositories.put(getKey(info.location), info);
} catch (MalformedURLException e) {
log("Error while restoring repository " + pathString, e); //$NON-NLS-1$
}
}
}
/**
* Restores the repository list.
*/
protected void restoreRepositories() {
synchronized (repositoryLock) {
repositories = new HashMap();
restoreFromSystemProperty();
restoreFromPreferences();
}
}
/*
* Save the repository list in the file-system
*/
private void saveToPreferences() {
try {
getPreferences().flush();
} catch (BackingStoreException e) {
log("Error while saving repositories in preferences", e); //$NON-NLS-1$
}
}
public IStatus validateRepositoryLocation(URL location, IProgressMonitor monitor) {
Assert.isNotNull(location);
IMetadataRepository result = getRepository(location);
if (result != null)
return Status.OK_STATUS;
String[] suffixes = getAllSuffixes();
SubMonitor sub = SubMonitor.convert(monitor, Messages.REPO_LOADING, suffixes.length * 100);
IStatus status = new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_NOT_FOUND, NLS.bind(Messages.repoMan_notExists, location.toExternalForm()), null);
for (int i = 0; i < suffixes.length; i++) {
SubMonitor loopMonitor = sub.newChild(100);
IExtension[] providers = findMatchingRepositoryExtensions(suffixes[i]);
// Loop over the candidates and return the first one that successfully loads
loopMonitor.beginTask("", providers.length * 10); //$NON-NLS-1$
for (int j = 0; j < providers.length; j++) {
IMetadataRepositoryFactory factory = (IMetadataRepositoryFactory) createExecutableExtension(providers[j], FACTORY);
if (factory != null) {
status = factory.validate(location, loopMonitor.newChild(10));
if (status.isOK()) {
sub.done();
return status;
}
}
}
}
sub.done();
return status;
}
}