| /******************************************************************************* |
| * Copyright (c) 2008, 2015 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 |
| * Wind River - fix for bug 299227 |
| * Sonatype, Inc. - transport split |
| * Red Hat,Inc. - fixes for bugs 249133, 460967 |
| * Ericsson AB (Pascal Rapicault) - reading preferences from base in shared install |
| *******************************************************************************/ |
| package org.eclipse.equinox.internal.p2.repository.helpers; |
| |
| import java.io.*; |
| import java.lang.ref.SoftReference; |
| import java.net.*; |
| import java.util.*; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.core.runtime.preferences.IPreferencesService; |
| import org.eclipse.equinox.internal.p2.core.helpers.*; |
| import org.eclipse.equinox.internal.p2.repository.Activator; |
| import org.eclipse.equinox.internal.p2.repository.Transport; |
| 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.repository.RepositoryEvent; |
| import org.eclipse.equinox.p2.core.*; |
| import org.eclipse.equinox.p2.core.spi.IAgentService; |
| import org.eclipse.equinox.p2.query.*; |
| import org.eclipse.equinox.p2.repository.IRepository; |
| import org.eclipse.equinox.p2.repository.IRepositoryManager; |
| import org.eclipse.equinox.security.storage.EncodingUtils; |
| import org.eclipse.osgi.util.NLS; |
| import org.osgi.service.prefs.BackingStoreException; |
| import org.osgi.service.prefs.Preferences; |
| |
| /** |
| * Common code shared between artifact and metadata repository managers. |
| */ |
| public abstract class AbstractRepositoryManager<T> implements IRepositoryManager<T>, IAgentService, ProvisioningListener { |
| protected static class RepositoryInfo<R> { |
| public String description; |
| public boolean isEnabled = true; |
| public boolean isSystem = false; |
| public URI location; |
| public String name; |
| public String nickname; |
| public SoftReference<IRepository<R>> repository; |
| public String suffix; |
| |
| public RepositoryInfo() { |
| super(); |
| } |
| } |
| |
| public static final String ATTR_SUFFIX = "suffix"; //$NON-NLS-1$ |
| public static final String EL_FACTORY = "factory"; //$NON-NLS-1$ |
| public static final String EL_FILTER = "filter"; //$NON-NLS-1$ |
| public static final String KEY_DESCRIPTION = "description"; //$NON-NLS-1$ |
| public static final String KEY_ENABLED = "enabled"; //$NON-NLS-1$ |
| public static final String KEY_NAME = "name"; //$NON-NLS-1$ |
| public static final String KEY_NICKNAME = "nickname"; //$NON-NLS-1$ |
| public static final String KEY_PROVIDER = "provider"; //$NON-NLS-1$ |
| public static final String KEY_SUFFIX = "suffix"; //$NON-NLS-1$ |
| public static final String KEY_SYSTEM = "isSystem"; //$NON-NLS-1$ |
| public static final String KEY_TYPE = "type"; //$NON-NLS-1$ |
| public static final String KEY_URI = "uri"; //$NON-NLS-1$ |
| public static final String KEY_URL = "url"; //$NON-NLS-1$ |
| public static final String KEY_VERSION = "version"; //$NON-NLS-1$ |
| |
| public static final String NODE_REPOSITORIES = "repositories"; //$NON-NLS-1$ |
| private static final String INDEX_FILE = "p2.index"; //$NON-NLS-1$ |
| |
| /** |
| * Map of String->RepositoryInfo, where String is the repository key |
| * obtained via getKey(URI). |
| */ |
| protected Map<String, RepositoryInfo<T>> repositories = null; |
| |
| //lock object to be held when referring to the repositories field |
| protected 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. |
| */ |
| protected SoftReference<List<URI>> unavailableRepositories; |
| |
| /** |
| * Set used to manage exclusive load locks on repository locations. |
| */ |
| private final Map<URI, Thread> loadLocks = new HashMap<URI, Thread>(); |
| private final IAgentLocation agentLocation; |
| protected final IProvisioningEventBus eventBus; |
| protected final IProvisioningAgent agent; |
| |
| protected AbstractRepositoryManager(IProvisioningAgent agent) { |
| super(); |
| this.agent = agent; |
| agentLocation = (IAgentLocation) agent.getService(IAgentLocation.SERVICE_NAME); |
| eventBus = (IProvisioningEventBus) agent.getService(IProvisioningEventBus.SERVICE_NAME); |
| eventBus.addListener(this); |
| } |
| |
| /** |
| * Adds a repository to the list of known repositories |
| * @param repository the repository object to add |
| * @param signalAdd whether a repository change event should be fired |
| * @param suffix the suffix used to load the repository, or <code>null</code> if unknown |
| */ |
| protected void addRepository(IRepository<T> repository, boolean signalAdd, String suffix) { |
| boolean added = false; |
| synchronized (repositoryLock) { |
| if (repositories == null) |
| restoreRepositories(); |
| String key = getKey(repository.getLocation()); |
| RepositoryInfo<T> info = repositories.get(key); |
| if (info == null) { |
| info = new RepositoryInfo<T>(); |
| added = true; |
| repositories.put(key, info); |
| } |
| info.repository = new SoftReference<IRepository<T>>(repository); |
| info.name = repository.getName(); |
| info.description = repository.getDescription(); |
| info.location = repository.getLocation(); |
| String value = repository.getProperties().get(IRepository.PROP_SYSTEM); |
| if (value != null) |
| info.isSystem = Boolean.parseBoolean(value); |
| info.suffix = suffix; |
| } |
| // save the given repository in the preferences. |
| remember(repository, suffix); |
| if (added && signalAdd) |
| broadcastChangeEvent(repository.getLocation(), getRepositoryType(), RepositoryEvent.ADDED, true); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.equinox.internal.provisional.p2.core.repository.IRepositoryManager#addRepository(java.net.URI) |
| */ |
| public void addRepository(URI location) { |
| checkValidLocation(location); |
| //add the repository, or enable it if already known |
| if (!addRepository(location, true, true)) |
| setEnabled(location, true); |
| } |
| |
| /** |
| * Adds the repository to the list of known repositories. |
| * @param location The repository location |
| * @param isEnabled Whether the repository should be enabled |
| * @param signalAdd Whether a repository add event should be broadcast |
| * @return <code>true</code> if the repository was actually added, and |
| * <code>false</code> otherwise. |
| */ |
| private boolean addRepository(URI location, boolean isEnabled, boolean signalAdd) { |
| RepositoryInfo<T> info = new RepositoryInfo<T>(); |
| info.location = location; |
| info.isEnabled = isEnabled; |
| boolean added = true; |
| synchronized (repositoryLock) { |
| if (repositories == null) |
| restoreRepositories(); |
| if (contains(location)) |
| return false; |
| added = repositories.put(getKey(location), info) == null; |
| // save the given repository in the preferences. |
| remember(info, true); |
| } |
| if (added && signalAdd) |
| broadcastChangeEvent(location, getRepositoryType(), RepositoryEvent.ADDED, isEnabled); |
| return added; |
| } |
| |
| protected IRepository<T> basicGetRepository(URI location) { |
| checkValidLocation(location); |
| synchronized (repositoryLock) { |
| if (repositories == null) |
| restoreRepositories(); |
| RepositoryInfo<T> info = repositories.get(getKey(location)); |
| if (info == null || info.repository == null) |
| return null; |
| IRepository<T> repo = info.repository.get(); |
| //update our repository info because the repository may have changed |
| if (repo != null) |
| addRepository(repo, false, info.suffix); |
| return repo; |
| } |
| } |
| |
| public IRepository<T> basicRefreshRepository(URI location, IProgressMonitor monitor) throws ProvisionException { |
| checkValidLocation(location); |
| clearNotFound(location); |
| boolean wasEnabled = isEnabled(location); |
| String nick = getRepositoryProperty(location, IRepository.PROP_NICKNAME); |
| //remove the repository so event is broadcast and repositories can clear their caches |
| if (!removeRepository(location)) |
| fail(location, ProvisionException.REPOSITORY_NOT_FOUND); |
| boolean loaded = false; |
| try { |
| IRepository<T> result = loadRepository(location, monitor, null, 0); |
| loaded = true; |
| setEnabled(location, wasEnabled); |
| return result; |
| } finally { |
| //if we failed to load, make sure the repository is not lost |
| if (!loaded) |
| addRepository(location, wasEnabled, true); |
| if (nick != null) |
| setRepositoryProperty(location, IRepository.PROP_NICKNAME, nick); |
| } |
| } |
| |
| private void broadcastChangeEvent(URI location, int repositoryType, int kind, boolean isEnabled) { |
| if (eventBus != null) |
| eventBus.publishEvent(new RepositoryEvent(location, repositoryType, kind, isEnabled)); |
| } |
| |
| /** |
| * 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(URI location) { |
| if (unavailableRepositories == null) |
| return false; |
| List<URI> badRepos = 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(URI location) { |
| List<URI> badRepos; |
| if (unavailableRepositories != null) { |
| badRepos = unavailableRepositories.get(); |
| if (badRepos != null) { |
| badRepos.remove(location); |
| return; |
| } |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.equinox.internal.provisional.p2.core.repository.IRepositoryManager#contains(java.net.URI) |
| */ |
| public boolean contains(URI location) { |
| checkValidLocation(location); |
| synchronized (repositoryLock) { |
| if (repositories == null) |
| restoreRepositories(); |
| return repositories.containsKey(getKey(location)); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepositoryManager#createRepository(java.net.URL, java.lang.String, java.lang.String, java.util.Map) |
| */ |
| protected IRepository<T> doCreateRepository(URI location, String name, String type, Map<String, String> properties) throws ProvisionException { |
| checkValidLocation(location); |
| Assert.isNotNull(name); |
| Assert.isNotNull(type); |
| IRepository<T> result = null; |
| try { |
| enterLoad(location, new NullProgressMonitor()); |
| boolean loaded = false; |
| try { |
| //repository should not already exist |
| loadRepository(location, (IProgressMonitor) null, type, 0); |
| loaded = true; |
| } catch (ProvisionException e) { |
| //expected - fall through and create the new repository |
| } |
| if (loaded) |
| fail(location, ProvisionException.REPOSITORY_EXISTS); |
| |
| IExtension extension = RegistryFactory.getRegistry().getExtension(getRepositoryProviderExtensionPointId(), type); |
| if (extension == null) |
| fail(location, ProvisionException.REPOSITORY_UNKNOWN_TYPE); |
| // MetadataRepositoryFactory factory = (MetadataRepositoryFactory) createExecutableExtension(extension, EL_FACTORY); |
| // if (factory == null) |
| // fail(location, ProvisionException.REPOSITORY_FAILED_READ); |
| result = factoryCreate(location, name, type, properties, extension); |
| if (result == null) |
| fail(location, ProvisionException.REPOSITORY_FAILED_READ); |
| clearNotFound(location); |
| addRepository(result, false, null); |
| } finally { |
| exitLoad(location); |
| } |
| //fire event after releasing load lock |
| broadcastChangeEvent(location, getRepositoryType(), RepositoryEvent.ADDED, true); |
| return result; |
| } |
| |
| /** |
| * Returns the executable extension, or <code>null</code> if there |
| * was no corresponding extension, or an error occurred loading it |
| */ |
| protected Object createExecutableExtension(IExtension extension, String element) { |
| IConfigurationElement[] elements = extension.getConfigurationElements(); |
| 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(), e); //$NON-NLS-1$ |
| return null; |
| } |
| } |
| } |
| log("Malformed repository extension: " + extension.getUniqueIdentifier(), null); //$NON-NLS-1$ |
| return null; |
| } |
| |
| /** |
| * Obtains an exclusive right to load a repository at the given location. Blocks |
| * if another thread is currently loading at that location. Invocation of this |
| * method must be followed by a subsequent call to {@link #exitLoad(URI)}. |
| * |
| * To avoid deadlock between the loadLock and repositoryLock, this method |
| * must not be called when repositoryLock is held. |
| * |
| * @param location The location to lock |
| */ |
| private void enterLoad(URI location, IProgressMonitor monitor) { |
| Thread current = Thread.currentThread(); |
| synchronized (loadLocks) { |
| while (true) { |
| Thread owner = loadLocks.get(location); |
| if (owner == null || current.equals(owner)) |
| break; |
| if (monitor.isCanceled()) |
| throw new OperationCanceledException(); |
| try { |
| loadLocks.wait(1000); |
| } catch (InterruptedException e) { |
| //keep trying |
| } |
| } |
| loadLocks.put(location, current); |
| } |
| } |
| |
| /** |
| * Relinquishes the exclusive right to load a repository at the given location. Unblocks |
| * other threads waiting to load at that location. |
| * @param location The location to unlock |
| */ |
| private void exitLoad(URI location) { |
| synchronized (loadLocks) { |
| loadLocks.remove(location); |
| loadLocks.notifyAll(); |
| } |
| } |
| |
| /** |
| * Creates and returns a repository using the given repository factory extension. Returns |
| * null if no factory could be found associated with that extension. |
| */ |
| protected abstract IRepository<T> factoryCreate(URI location, String name, String type, Map<String, String> properties, IExtension extension) throws ProvisionException; |
| |
| /** |
| * Loads and returns a repository using the given repository factory extension. Returns |
| * null if no factory could be found associated with that extension. |
| */ |
| protected abstract IRepository<T> factoryLoad(URI location, IExtension extension, int flags, SubMonitor monitor) throws ProvisionException; |
| |
| private void fail(URI location, int code) throws ProvisionException { |
| 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_FAILED_AUTHENTICATION : |
| msg = NLS.bind(Messages.repoManAuthenticationFailedFor_0, location); |
| break; |
| } |
| if (msg == null) |
| msg = Messages.repoMan_internalError; |
| throw new ProvisionException(new Status(IStatus.ERROR, getBundleId(), code, msg, null)); |
| } |
| |
| protected IExtension[] findMatchingRepositoryExtensions(String suffix, String type) { |
| IConfigurationElement[] elt = null; |
| if (type != null && type.length() > 0) { |
| IExtension ext = RegistryFactory.getRegistry().getExtension(getRepositoryProviderExtensionPointId(), type); |
| elt = (ext != null) ? ext.getConfigurationElements() : new IConfigurationElement[0]; |
| } else { |
| elt = RegistryFactory.getRegistry().getConfigurationElementsFor(getRepositoryProviderExtensionPointId()); |
| } |
| int count = 0; |
| for (int i = 0; i < elt.length; i++) { |
| if (EL_FILTER.equals(elt[i].getName())) { |
| if (!suffix.equals(elt[i].getAttribute(ATTR_SUFFIX))) { |
| 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; |
| } |
| |
| protected String[] getAllSuffixes() { |
| final IExtensionRegistry registry = RegistryFactory.getRegistry(); |
| if (registry == null) { |
| log("Extension registry not found", new RuntimeException()); //$NON-NLS-1$ |
| return new String[0]; |
| } |
| IConfigurationElement[] elements = registry.getConfigurationElementsFor(getRepositoryProviderExtensionPointId()); |
| ArrayList<String> result = new ArrayList<String>(elements.length); |
| result.add(getDefaultSuffix()); |
| 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 result.toArray(new String[result.size()]); |
| } |
| |
| /** |
| * Returns the bundle id of the bundle that provides the concrete repository manager |
| * @return a symbolic bundle id |
| */ |
| protected abstract String getBundleId(); |
| |
| /** |
| * Returns the default repository suffix. This is used to ensure a particular |
| * repository type is preferred over all others. |
| */ |
| protected abstract String getDefaultSuffix(); |
| |
| /* |
| * Return a string key based on the given repository location which |
| * is suitable for use as a preference node name. |
| * TODO: convert local file system URI to canonical form |
| */ |
| private String getKey(URI location) { |
| String key = location.toString().replace('/', '_'); |
| //remove trailing slash |
| if (key.endsWith("_")) //$NON-NLS-1$ |
| key = key.substring(0, key.length() - 1); |
| return key; |
| } |
| |
| public IProvisioningAgent getAgent() { |
| return agent; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.equinox.internal.provisional.p2.core.repository.IRepositoryManager#getKnownRepositories(int) |
| */ |
| public URI[] getKnownRepositories(int flags) { |
| synchronized (repositoryLock) { |
| if (repositories == null) |
| restoreRepositories(); |
| ArrayList<URI> result = new ArrayList<URI>(); |
| for (RepositoryInfo<T> info : repositories.values()) { |
| if (matchesFlags(info, flags)) |
| result.add(info.location); |
| } |
| return result.toArray(new URI[result.size()]); |
| } |
| } |
| |
| /** |
| * Return the preference node which is the root for where we store the repository information. |
| * Returns <code>null</code> if no preferences are available |
| */ |
| Preferences getPreferences() { |
| if (agentLocation == null) |
| return null; |
| IPreferencesService prefService = ServiceHelper.getService(Activator.getContext(), IPreferencesService.class); |
| if (prefService == null) |
| return null; |
| try { |
| //see ProfileScope for preference path format |
| String locationString = EncodingUtils.encodeSlashes(agentLocation.getRootLocation().toString()); |
| return prefService.getRootNode().node("/profile/" + locationString + "/_SELF_/" + getBundleId() + '/' + NODE_REPOSITORIES); //$NON-NLS-1$ //$NON-NLS-2$ |
| } catch (IllegalArgumentException e) { |
| return null; |
| } |
| } |
| |
| Preferences getSharedPreferences() { |
| if (agentLocation == null) |
| return null; |
| IPreferencesService prefService = ServiceHelper.getService(Activator.getContext(), IPreferencesService.class); |
| if (prefService == null) |
| return null; |
| try { |
| //see ProfileScope for preference path format |
| String locationString = EncodingUtils.encodeSlashes(agentLocation.getRootLocation().toString()); |
| return prefService.getRootNode().node("/profile/shared/" + locationString + "/_SELF_/" + getBundleId() + '/' + NODE_REPOSITORIES); //$NON-NLS-1$ //$NON-NLS-2$ |
| } catch (IllegalArgumentException e) { |
| return null; |
| } |
| } |
| |
| /** |
| * Restores a repository location from the preferences. |
| */ |
| private URI getRepositoryLocation(Preferences node) { |
| //prefer the location stored in URI form |
| String locationString = node.get(KEY_URI, null); |
| try { |
| if (locationString != null) { |
| URI result = new URI(locationString); |
| if (result.isAbsolute()) |
| return result; |
| log("Invalid repository URI: " + locationString, new RuntimeException()); //$NON-NLS-1$ |
| } |
| } catch (URISyntaxException e) { |
| log("Error while restoring repository: " + locationString, e); //$NON-NLS-1$ |
| } |
| //we used to store the repository as a URL, so try old key for backwards compatibility |
| locationString = node.get(KEY_URL, null); |
| try { |
| if (locationString != null) { |
| URI result = URIUtil.toURI(new URL(locationString)); |
| if (result.isAbsolute()) |
| return result; |
| log("Invalid repository URL: " + locationString, new RuntimeException()); //$NON-NLS-1$ |
| } |
| } catch (MalformedURLException e) { |
| log("Error while restoring repository: " + locationString, e); //$NON-NLS-1$ |
| } catch (URISyntaxException e) { |
| log("Error while restoring repository: " + locationString, e); //$NON-NLS-1$ |
| } |
| return null; |
| } |
| |
| /*(non-Javadoc) |
| * @see org.eclipse.equinox.internal.provisional.p2.core.repository.IRepositoryManager#getRepositoryProperty(java.net.URI, java.lang.String) |
| */ |
| public String getRepositoryProperty(URI location, String key) { |
| checkValidLocation(location); |
| synchronized (repositoryLock) { |
| if (repositories == null) |
| restoreRepositories(); |
| RepositoryInfo<T> info = repositories.get(getKey(location)); |
| if (info == null) |
| return null;// Repository not found |
| if (IRepository.PROP_DESCRIPTION.equals(key)) |
| return info.description; |
| else if (IRepository.PROP_NAME.equals(key)) |
| return info.name; |
| else if (IRepository.PROP_SYSTEM.equals(key)) |
| return Boolean.toString(info.isSystem); |
| else if (IRepository.PROP_NICKNAME.equals(key)) |
| return info.nickname; |
| // Key not known, return null |
| return null; |
| } |
| } |
| |
| /*(non-Javadoc) |
| * @see org.eclipse.equinox.internal.provisional.p2.core.repository.IRepositoryManager#getRepositoryProperty(java.net.URI, java.lang.String) |
| */ |
| public void setRepositoryProperty(URI location, String key, String value) { |
| checkValidLocation(location); |
| synchronized (repositoryLock) { |
| if (repositories == null) |
| restoreRepositories(); |
| RepositoryInfo<T> info = repositories.get(getKey(location)); |
| if (info == null) |
| return;// Repository not found |
| if (IRepository.PROP_DESCRIPTION.equals(key)) |
| info.description = value; |
| else if (IRepository.PROP_NAME.equals(key)) |
| info.name = value; |
| else if (IRepository.PROP_NICKNAME.equals(key)) |
| info.nickname = value; |
| else if (IRepository.PROP_SYSTEM.equals(key)) |
| //only true if value.equals("true") which is OK because a repository is only system if it's explicitly set to system. |
| info.isSystem = Boolean.parseBoolean(value); |
| remember(info, true); |
| } |
| } |
| |
| /** |
| * Returns the fully qualified id of the repository provider extension point. |
| */ |
| protected abstract String getRepositoryProviderExtensionPointId(); |
| |
| /** |
| * Returns the system property used to specify additional repositories to be |
| * automatically added to the list of known repositories. |
| */ |
| protected abstract String getRepositorySystemProperty(); |
| |
| /** |
| * Returns the repository type stored in this manager. |
| */ |
| protected abstract int getRepositoryType(); |
| |
| /** |
| * Returns the preferred search order for this location |
| */ |
| protected abstract String[] getPreferredRepositorySearchOrder(LocationProperties properties); |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.equinox.internal.provisional.p2.core.repository.IRepositoryManager#isEnabled(java.net.URI) |
| */ |
| public boolean isEnabled(URI location) { |
| synchronized (repositoryLock) { |
| if (repositories == null) |
| restoreRepositories(); |
| RepositoryInfo<T> info = repositories.get(getKey(location)); |
| if (info != null) |
| return info.isEnabled; |
| // Repository not found, return false |
| return false; |
| } |
| } |
| |
| protected IRepository<T> loadRepository(URI location, IProgressMonitor monitor, String type, int flags) throws ProvisionException { |
| checkValidLocation(location); |
| SubMonitor sub = SubMonitor.convert(monitor, 100); |
| boolean added = false; |
| IRepository<T> result = null; |
| |
| try { |
| enterLoad(location, sub.newChild(5)); |
| result = basicGetRepository(location); |
| if (result != null) |
| return result; |
| if (checkNotFound(location)) |
| fail(location, ProvisionException.REPOSITORY_NOT_FOUND); |
| //add the repository first so that it will be enabled, but don't send add event until after the load |
| added = addRepository(location, true, false); |
| |
| LocationProperties indexFile = loadIndexFile(location, sub.newChild(15)); |
| String[] preferredOrder = getPreferredRepositorySearchOrder(indexFile); |
| String[] suffixes = sortSuffixes(getAllSuffixes(), location, preferredOrder); |
| |
| sub = SubMonitor.convert(sub, NLS.bind(Messages.repoMan_adding, location), suffixes.length * 100); |
| ProvisionException failure = null; |
| try { |
| for (int i = 0; i < suffixes.length; i++) { |
| if (sub.isCanceled()) |
| throw new OperationCanceledException(); |
| try { |
| result = loadRepository(location, suffixes[i], type, flags, sub.newChild(100)); |
| } catch (ProvisionException e) { |
| failure = e; |
| break; |
| } |
| if (result != null) { |
| addRepository(result, false, suffixes[i]); |
| break; |
| } |
| } |
| } finally { |
| sub.done(); |
| } |
| if (result == null) { |
| //if we just added the repository, remove it because it cannot be loaded |
| if (added) |
| removeRepository(location, false); |
| //eagerly cleanup missing system repositories |
| if (Boolean.parseBoolean(getRepositoryProperty(location, IRepository.PROP_SYSTEM))) |
| removeRepository(location); |
| else if (failure == null || (failure.getStatus().getCode() != ProvisionException.REPOSITORY_FAILED_AUTHENTICATION && failure.getStatus().getCode() != ProvisionException.REPOSITORY_FAILED_READ)) |
| rememberNotFound(location); |
| if (failure != null) |
| throw failure; |
| fail(location, ProvisionException.REPOSITORY_NOT_FOUND); |
| } |
| } finally { |
| exitLoad(location); |
| } |
| //broadcast the add event after releasing lock |
| if (added) |
| broadcastChangeEvent(location, getRepositoryType(), RepositoryEvent.ADDED, true); |
| return result; |
| } |
| |
| /** |
| * Fetches the p2.index file from the server. If the file could not be fetched |
| * a NullSafe version is returned. |
| */ |
| private LocationProperties loadIndexFile(URI location, IProgressMonitor monitor) { |
| LocationProperties locationProperties = LocationProperties.createEmptyIndexFile(); |
| //Handle the case of in-memory repos |
| if (!isURL(location)) |
| return locationProperties; |
| |
| if ("file".equals(location.getScheme())) { //$NON-NLS-1$ |
| InputStream localStream = null; |
| try { |
| try { |
| File indexFile = URIUtil.toFile(getIndexFileURI(location)); |
| if (indexFile != null && indexFile.exists() && indexFile.canRead()) { |
| localStream = new FileInputStream(indexFile); |
| locationProperties = LocationProperties.create(localStream); |
| } |
| } finally { |
| if (localStream != null) |
| localStream.close(); |
| } |
| } catch (IOException e) { |
| //do nothing. |
| } |
| return locationProperties; |
| } |
| |
| //Handle non local repos (i.e. not file:) |
| ByteArrayOutputStream index = new ByteArrayOutputStream(); |
| IStatus indexFileStatus = null; |
| indexFileStatus = getTransport().download(getIndexFileURI(location), index, monitor); |
| if (indexFileStatus != null && indexFileStatus.isOK()) |
| locationProperties = LocationProperties.create(new ByteArrayInputStream(index.toByteArray())); |
| |
| return locationProperties; |
| } |
| |
| /** |
| * Basic sanity checking on location argument |
| */ |
| private URI checkValidLocation(URI location) { |
| if (location == null) |
| throw new IllegalArgumentException("Location cannot be null"); //$NON-NLS-1$ |
| if (!location.isAbsolute()) |
| throw new IllegalArgumentException("Location must be absolute: " + location); //$NON-NLS-1$ |
| return location; |
| } |
| |
| private static boolean isURL(URI location) { |
| try { |
| new URL(location.toASCIIString()); |
| } catch (MalformedURLException e) { |
| return false; |
| } |
| return true; |
| } |
| |
| private IRepository<T> loadRepository(URI location, String suffix, String type, int flags, SubMonitor monitor) throws ProvisionException { |
| IExtension[] providers = findMatchingRepositoryExtensions(suffix, type); |
| // Loop over the candidates and return the first one that successfully loads |
| monitor.beginTask(null, providers.length * 10); |
| for (int i = 0; i < providers.length; i++) |
| try { |
| IRepository<T> repo = factoryLoad(location, providers[i], flags, monitor); |
| if (repo != null) |
| return repo; |
| } catch (ProvisionException e) { |
| if (e.getStatus().getCode() != ProvisionException.REPOSITORY_NOT_FOUND) |
| throw e; |
| } catch (OperationCanceledException e) { |
| //always propagate cancelation |
| throw e; |
| } catch (Exception e) { |
| //catch and log unexpected errors and move onto the next factory |
| log("Unexpected error loading extension: " + providers[i].getUniqueIdentifier(), e); //$NON-NLS-1$ |
| } catch (LinkageError e) { |
| //catch and log unexpected errors and move onto the next factory |
| log("Unexpected error loading extension: " + providers[i].getUniqueIdentifier(), e); //$NON-NLS-1$ |
| } |
| return null; |
| } |
| |
| protected void log(String message, Throwable t) { |
| LogHelper.log(new Status(IStatus.ERROR, getBundleId(), message, t)); |
| } |
| |
| private boolean matchesFlags(RepositoryInfo<T> 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_DISABLED) == REPOSITORIES_DISABLED) { |
| if (info.isEnabled) |
| return false; |
| } else { |
| //ignore disabled repositories for all other flag types |
| if (!info.isEnabled) |
| return false; |
| } |
| if ((flags & REPOSITORIES_LOCAL) == REPOSITORIES_LOCAL) |
| return "file".equals(info.location.getScheme()) || info.location.toString().startsWith("jar:file"); //$NON-NLS-1$ //$NON-NLS-2$ |
| if ((flags & REPOSITORIES_NON_LOCAL) == REPOSITORIES_NON_LOCAL) |
| return !("file".equals(info.location.getScheme()) || info.location.toString().startsWith("jar:file")); //$NON-NLS-1$ //$NON-NLS-2$ |
| 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() == getRepositoryType()) |
| addRepository(event.getRepositoryLocation(), event.isRepositoryEnabled(), true); |
| } |
| } |
| |
| /** |
| * Sets a preference and returns <code>true</code> if the preference |
| * was actually changed. |
| */ |
| protected 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; |
| } |
| |
| /* |
| * Add the given repository object to the preferences and save. |
| */ |
| private void remember(IRepository<T> repository, String suffix) { |
| boolean changed = false; |
| Preferences node = getPreferences(); |
| // Ensure we retrieved preferences |
| if (node == null) |
| return; |
| node = node.node(getKey(repository.getLocation())); |
| |
| try { |
| changed |= putValue(node, KEY_URI, repository.getLocation().toString()); |
| changed |= putValue(node, KEY_URL, null); |
| 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()); |
| //allow repository manager to define system property if it is undefined in the repository itself |
| String value = repository.getProperties().get(IRepository.PROP_SYSTEM); |
| if (value != null) |
| changed |= putValue(node, KEY_SYSTEM, value); |
| changed |= putValue(node, KEY_SUFFIX, suffix); |
| if (changed) |
| saveToPreferences(); |
| } catch (IllegalStateException e) { |
| //the repository was removed concurrently, so we don't need to save it |
| } |
| } |
| |
| /** |
| * Writes the state of the repository information into the appropriate preference node. |
| * |
| * @param info The info to write to the preference node |
| * @param flush <code>true</code> if the preference node should be flushed to |
| * disk, and <code>false</code> otherwise |
| */ |
| private boolean remember(RepositoryInfo<T> info, boolean flush) { |
| boolean changed = false; |
| Preferences node = getPreferences(); |
| // Ensure we retrieved preferences |
| if (node == null) |
| return changed; |
| node = node.node(getKey(info.location)); |
| try { |
| changed |= putValue(node, KEY_URI, info.location.toString()); |
| changed |= putValue(node, KEY_URL, null); |
| changed |= putValue(node, KEY_SYSTEM, Boolean.toString(info.isSystem)); |
| changed |= putValue(node, KEY_DESCRIPTION, info.description); |
| changed |= putValue(node, KEY_NAME, info.name); |
| changed |= putValue(node, KEY_NICKNAME, info.nickname); |
| changed |= putValue(node, KEY_SUFFIX, info.suffix); |
| changed |= putValue(node, KEY_ENABLED, Boolean.toString(info.isEnabled)); |
| if (changed && flush) |
| saveToPreferences(); |
| return changed; |
| } catch (IllegalStateException e) { |
| //the repository was removed concurrently, so we don't need to save it |
| return false; |
| } |
| } |
| |
| /** |
| * Cache the fact that we tried to load a repository at this location and did not find anything. |
| */ |
| private void rememberNotFound(URI location) { |
| List<URI> badRepos; |
| if (unavailableRepositories != null) { |
| badRepos = unavailableRepositories.get(); |
| if (badRepos != null) { |
| badRepos.add(location); |
| return; |
| } |
| } |
| badRepos = new ArrayList<URI>(); |
| badRepos.add(location); |
| unavailableRepositories = new SoftReference<List<URI>>(badRepos); |
| } |
| |
| public boolean removeRepository(URI toRemove) { |
| return removeRepository(checkValidLocation(toRemove), true); |
| } |
| |
| private boolean removeRepository(URI toRemove, boolean signalRemove) { |
| 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 { |
| if (Tracing.DEBUG_REMOVE_REPO) { |
| String msg = "Removing repository: " + toRemove; //$NON-NLS-1$ |
| Tracing.debug(msg); |
| new Exception(msg).printStackTrace(); |
| } |
| Preferences node = getPreferences(); |
| if (node != null) { |
| node.node(repoKey).removeNode(); |
| saveToPreferences(); |
| } |
| clearNotFound(toRemove); |
| } catch (BackingStoreException e) { |
| log("Error saving preferences", e); //$NON-NLS-1$ |
| } |
| //TODO: compute and pass appropriate isEnabled flag |
| if (signalRemove) |
| broadcastChangeEvent(toRemove, getRepositoryType(), RepositoryEvent.REMOVED, true); |
| return true; |
| } |
| |
| /* |
| * Load the list of repositories from the preferences. |
| */ |
| private void basicRestoreFromPreferences(Preferences node, boolean save) { |
| // restore the list of repositories from the preference store |
| if (node == null) |
| return; |
| 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]); |
| URI location = getRepositoryLocation(child); |
| if (location == null) { |
| try { |
| child.removeNode(); |
| continue; |
| } catch (BackingStoreException e) { |
| log("Error removing invalid repository", e); //$NON-NLS-1$ |
| } |
| } |
| RepositoryInfo<T> info = new RepositoryInfo<T>(); |
| info.location = location; |
| info.name = child.get(KEY_NAME, null); |
| info.nickname = child.get(KEY_NICKNAME, null); |
| info.description = child.get(KEY_DESCRIPTION, null); |
| info.isSystem = child.getBoolean(KEY_SYSTEM, false); |
| info.isEnabled = child.getBoolean(KEY_ENABLED, true); |
| info.suffix = child.get(KEY_SUFFIX, null); |
| repositories.put(getKey(info.location), info); |
| } |
| // now that we have loaded everything, remember them |
| if (save) |
| saveToPreferences(); |
| } |
| |
| private void restoreFromSystemProperty() { |
| String locationString = Activator.getContext().getProperty(getRepositorySystemProperty()); |
| if (locationString != null) { |
| StringTokenizer tokenizer = new StringTokenizer(locationString, ","); //$NON-NLS-1$ |
| while (tokenizer.hasMoreTokens()) { |
| try { |
| addRepository(new URI(tokenizer.nextToken()), true, true); |
| } catch (URISyntaxException e) { |
| log("Error while restoring repository " + locationString, e); //$NON-NLS-1$ |
| } |
| } |
| } |
| } |
| |
| /** |
| * Restores the repository list. |
| */ |
| private void restoreRepositories() { |
| synchronized (repositoryLock) { |
| repositories = new HashMap<String, RepositoryInfo<T>>(); |
| restoreSpecialRepositories(); |
| restoreFromSystemProperty(); |
| basicRestoreFromPreferences(getSharedPreferences(), false); |
| basicRestoreFromPreferences(getPreferences(), true); |
| } |
| } |
| |
| /** |
| * Hook method to restore special additional repositories. |
| */ |
| protected void restoreSpecialRepositories() { |
| //by default no special repositories |
| } |
| |
| /* |
| * Save the list of repositories to the file-system. |
| */ |
| private void saveToPreferences() { |
| try { |
| Preferences node = getPreferences(); |
| if (node != null) |
| node.flush(); |
| } catch (BackingStoreException e) { |
| log("Error while saving repositories in preferences", e); //$NON-NLS-1$ |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.equinox.internal.provisional.p2.core.repository.IRepositoryManager#setEnabled(java.net.URI, boolean) |
| */ |
| public void setEnabled(URI location, boolean enablement) { |
| checkValidLocation(location); |
| synchronized (repositoryLock) { |
| if (repositories == null) |
| restoreRepositories(); |
| RepositoryInfo<T> info = repositories.get(getKey(location)); |
| if (info == null || info.isEnabled == enablement) |
| return; |
| info.isEnabled = enablement; |
| remember(info, true); |
| } |
| broadcastChangeEvent(location, getRepositoryType(), RepositoryEvent.ENABLEMENT, enablement); |
| } |
| |
| /*(non-Javadoc) |
| * @see org.eclipse.equinox.p2.core.spi.IAgentService#start() |
| */ |
| public void start() { |
| //nothing to do |
| } |
| |
| /*(non-Javadoc) |
| * @see org.eclipse.equinox.p2.core.spi.IAgentService#stop() |
| */ |
| public void stop() { |
| eventBus.removeListener(this); |
| //ensure all repository state in memory is written to disk |
| boolean changed = false; |
| synchronized (repositoryLock) { |
| if (repositories != null) { |
| for (RepositoryInfo<T> info : repositories.values()) { |
| changed |= remember(info, false); |
| } |
| } |
| } |
| if (changed) { |
| if (Tracing.DEBUG) |
| Tracing.debug("Unsaved preferences when shutting down " + getClass().getName()); //$NON-NLS-1$ |
| saveToPreferences(); |
| } |
| repositories = null; |
| unavailableRepositories = null; |
| } |
| |
| /** |
| * Optimize the order in which repository suffixes are searched by trying |
| * the last successfully loaded suffix first. |
| * @nooverride This method is not intended to be re-implemented or extended by clients. |
| * @noreference This method is not intended to be referenced by clients. |
| */ |
| protected String[] sortSuffixes(String[] suffixes, URI location, String[] preferredOrder) { |
| String[] result = new String[suffixes.length]; |
| System.arraycopy(suffixes, 0, result, 0, suffixes.length); |
| |
| synchronized (repositoryLock) { |
| if (repositories == null) |
| restoreRepositories(); |
| RepositoryInfo<T> info = repositories.get(getKey(location)); |
| if (info != null && info.suffix != null) { |
| //move lastSuffix to the front of the list but preserve order of remaining entries |
| String lastSuffix = info.suffix; |
| for (int i = 0; i < result.length; i++) { |
| if (lastSuffix.equals(result[i])) { |
| System.arraycopy(result, 0, result, 1, i); |
| result[0] = lastSuffix; |
| break; |
| } |
| } |
| } |
| // Now make sure that anything in the "preferredOrder" is at the top |
| if (preferredOrder != null) { |
| int priority = 0; |
| for (int i = 0; i < preferredOrder.length; i++) { |
| String currentSuffix = preferredOrder[i]; |
| if (LocationProperties.END.equals(currentSuffix.trim())) { |
| // All suffixes from here on should be ignored |
| String[] tmp = new String[priority]; |
| System.arraycopy(result, 0, tmp, 0, priority); |
| return tmp; |
| } |
| for (int j = priority; j < result.length; j++) { |
| if (result[j].equalsIgnoreCase(currentSuffix.trim())) { |
| String tmp = result[j]; |
| System.arraycopy(result, priority, result, priority + 1, j - priority); |
| result[priority] = tmp; |
| priority++; |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Performs a query against the contents 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 |
| * 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 element in each known repository |
| * @param monitor a progress monitor, or <code>null</code> if progress |
| * reporting is not desired |
| * @return A collector containing the results of the query |
| */ |
| public IQueryResult<T> query(IQuery<T> query, IProgressMonitor monitor) { |
| URI[] locations = getKnownRepositories(REPOSITORIES_ALL); |
| List<IRepository<T>> queryables = new ArrayList<IRepository<T>>(locations.length); // use a list since we don't know exactly how many will load |
| SubMonitor sub = SubMonitor.convert(monitor, locations.length * 10); |
| for (int i = 0; i < locations.length; i++) { |
| try { |
| if (sub.isCanceled()) |
| throw new OperationCanceledException(); |
| queryables.add(loadRepository(locations[i], sub.newChild(9), null, 0)); |
| } catch (ProvisionException e) { |
| //ignore this repository for this query |
| } |
| } |
| try { |
| IQueryable<T> compoundQueryable = QueryUtil.compoundQueryable(queryables); |
| return compoundQueryable.query(query, sub.newChild(locations.length * 1)); |
| } finally { |
| sub.done(); |
| } |
| } |
| |
| private static URI getIndexFileURI(URI base) { |
| final String name = INDEX_FILE; |
| String spec = base.toString(); |
| if (spec.endsWith(name)) |
| return base; |
| return URIUtil.append(base, name); |
| } |
| |
| protected Transport getTransport() { |
| return (Transport) agent.getService(Transport.SERVICE_NAME); |
| } |
| |
| public void flushCache() { |
| synchronized (repositories) { |
| Collection<RepositoryInfo<T>> repos = repositories.values(); |
| for (Iterator<RepositoryInfo<T>> iterator = repos.iterator(); iterator.hasNext();) { |
| RepositoryInfo<T> repositoryInfo = iterator.next(); |
| repositoryInfo.repository = null; |
| } |
| } |
| } |
| } |