| /******************************************************************************* |
| * Copyright (c) 2008, 2017 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 |
| * WindRiver - https://bugs.eclipse.org/bugs/show_bug.cgi?id=227372 |
| * Sonatype, Inc. - ongoing development |
| *******************************************************************************/ |
| package org.eclipse.equinox.p2.engine; |
| |
| import java.net.URI; |
| import java.util.*; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.equinox.internal.p2.engine.DebugHelper; |
| import org.eclipse.equinox.p2.core.IProvisioningAgent; |
| import org.eclipse.equinox.p2.core.ProvisionException; |
| import org.eclipse.equinox.p2.metadata.IArtifactKey; |
| import org.eclipse.equinox.p2.metadata.IInstallableUnit; |
| import org.eclipse.equinox.p2.query.*; |
| import org.eclipse.equinox.p2.repository.*; |
| import org.eclipse.equinox.p2.repository.artifact.*; |
| import org.eclipse.equinox.p2.repository.metadata.IMetadataRepository; |
| import org.eclipse.equinox.p2.repository.metadata.IMetadataRepositoryManager; |
| |
| /** |
| * A provisioning context defines the scope in which a provisioning operation |
| * occurs. A context can be used to specify the set of repositories available |
| * to the planner and engine as they perform provisioning work. |
| * @since 2.0 |
| */ |
| public class ProvisioningContext { |
| private IProvisioningAgent agent; |
| private URI[] artifactRepositories; //artifact repositories to consult |
| private final List<IInstallableUnit> extraIUs = Collections.synchronizedList(new ArrayList<IInstallableUnit>()); |
| private URI[] metadataRepositories; //metadata repositories to consult |
| private final Map<String, String> properties = new HashMap<>(); |
| private Map<String, URI> referencedArtifactRepositories = null; |
| |
| private static final String FILE_PROTOCOL = "file"; //$NON-NLS-1$ |
| |
| class ArtifactRepositoryQueryable implements IQueryable<IArtifactRepository> { |
| List<IArtifactRepository> repositories; |
| |
| ArtifactRepositoryQueryable(List<IArtifactRepository> repositories) { |
| this.repositories = repositories; |
| } |
| |
| @Override |
| public IQueryResult<IArtifactRepository> query(IQuery<IArtifactRepository> query, IProgressMonitor mon) { |
| return query.perform(repositories.listIterator()); |
| } |
| } |
| |
| /** |
| * This Comparator sorts the repositories such that local repositories are first |
| */ |
| private static final Comparator<URI> LOCAL_FIRST_COMPARATOR = new Comparator<URI>() { |
| |
| @Override |
| public int compare(URI arg0, URI arg1) { |
| String protocol0 = arg0.getScheme(); |
| String protocol1 = arg1.getScheme(); |
| |
| if (FILE_PROTOCOL.equals(protocol0) && !FILE_PROTOCOL.equals(protocol1)) |
| return -1; |
| if (!FILE_PROTOCOL.equals(protocol0) && FILE_PROTOCOL.equals(protocol1)) |
| return 1; |
| return 0; |
| } |
| }; |
| |
| /** |
| * Instructs the provisioning context to follow metadata repository references when |
| * providing queryables for obtaining metadata and artifacts. When this property is set to |
| * "true", then metadata repository references that are encountered while loading the |
| * specified metadata repositories will be included in the provisioning |
| * context. |
| * |
| * @see #getMetadata(IProgressMonitor) |
| * @see #setMetadataRepositories(URI[]) |
| */ |
| public static final String FOLLOW_REPOSITORY_REFERENCES = "org.eclipse.equinox.p2.director.followRepositoryReferences"; //$NON-NLS-1$ |
| |
| private static final String FOLLOW_ARTIFACT_REPOSITORY_REFERENCES = "org.eclipse.equinox.p2.director.followArtifactRepositoryReferences"; //$NON-NLS-1$ |
| |
| /** |
| * Creates a new provisioning context that includes all available metadata and |
| * artifact repositories available to the specified provisioning agent. |
| * |
| * @param agent the provisioning agent from which to obtain any necessary services. |
| */ |
| public ProvisioningContext(IProvisioningAgent agent) { |
| this.agent = agent; |
| // null repos means look at them all |
| metadataRepositories = null; |
| artifactRepositories = null; |
| setProperty(FOLLOW_ARTIFACT_REPOSITORY_REFERENCES, Boolean.TRUE.toString()); |
| } |
| |
| /** |
| * Returns a queryable that can be used to obtain any artifact keys that |
| * are needed for the provisioning operation. |
| * |
| * @param monitor a progress monitor to be used when creating the queryable |
| * @return a queryable that can be used to query available artifact keys. |
| * |
| * @see #setArtifactRepositories(URI[]) |
| */ |
| public IQueryable<IArtifactKey> getArtifactKeys(IProgressMonitor monitor) { |
| return QueryUtil.compoundQueryable(getLoadedArtifactRepositories(monitor)); |
| } |
| |
| /** |
| * Returns a queryable that can be used to obtain any artifact descriptors that |
| * are needed for the provisioning operation. |
| * |
| * @param monitor a progress monitor to be used when creating the queryable |
| * @return a queryable that can be used to query available artifact descriptors. |
| * |
| * @see #setArtifactRepositories(URI[]) |
| */ |
| public IQueryable<IArtifactDescriptor> getArtifactDescriptors(IProgressMonitor monitor) { |
| List<IArtifactRepository> repos = getLoadedArtifactRepositories(monitor); |
| List<IQueryable<IArtifactDescriptor>> descriptorQueryables = new ArrayList<>(); |
| for (IArtifactRepository repo : repos) { |
| descriptorQueryables.add(repo.descriptorQueryable()); |
| } |
| return QueryUtil.compoundQueryable(descriptorQueryables); |
| } |
| |
| /** |
| * Returns a queryable that can be used to obtain any artifact repositories that |
| * are needed for the provisioning operation. |
| * |
| * @param monitor a progress monitor to be used when creating the queryable |
| * @return a queryable that can be used to query available artifact repositories. |
| * |
| * @see #setArtifactRepositories(URI[]) |
| */ |
| public IQueryable<IArtifactRepository> getArtifactRepositories(IProgressMonitor monitor) { |
| return new ArtifactRepositoryQueryable(getLoadedArtifactRepositories(monitor)); |
| } |
| |
| /** |
| * Return an array of loaded artifact repositories. |
| */ |
| private List<IArtifactRepository> getLoadedArtifactRepositories(IProgressMonitor monitor) { |
| IArtifactRepositoryManager repoManager = (IArtifactRepositoryManager) agent.getService(IArtifactRepositoryManager.SERVICE_NAME); |
| URI[] repositories = artifactRepositories == null ? repoManager.getKnownRepositories(IRepositoryManager.REPOSITORIES_ALL) : artifactRepositories; |
| Arrays.sort(repositories, LOCAL_FIRST_COMPARATOR); |
| |
| List<IArtifactRepository> repos = new ArrayList<>(); |
| SubMonitor sub = SubMonitor.convert(monitor, (repositories.length + 1) * 100); |
| for (int i = 0; i < repositories.length; i++) { |
| if (sub.isCanceled()) { |
| throw new OperationCanceledException(); |
| } |
| URI location = repositories[i]; |
| try { |
| repos.add(repoManager.loadRepository(location, sub.newChild(100))); |
| } catch (ProvisionException e) { |
| //skip unreadable repositories |
| } |
| // Remove this URI from the list of extra references if it is there. |
| if (referencedArtifactRepositories != null && location != null) { |
| referencedArtifactRepositories.remove(location.toString()); |
| } |
| } |
| // Are there any extra artifact repository references to consider? |
| if (referencedArtifactRepositories != null && referencedArtifactRepositories.size() > 0 && shouldFollowArtifactReferences()) { |
| SubMonitor innerSub = SubMonitor.convert(sub.newChild(100), referencedArtifactRepositories.size() * 100); |
| for (URI referencedURI : referencedArtifactRepositories.values()) { |
| try { |
| repos.add(repoManager.loadRepository(referencedURI, innerSub.newChild(100))); |
| } catch (ProvisionException e) { |
| // skip unreadable repositories |
| } |
| } |
| } |
| return repos; |
| } |
| |
| private Set<IMetadataRepository> getLoadedMetadataRepositories(IProgressMonitor monitor) { |
| IMetadataRepositoryManager repoManager = (IMetadataRepositoryManager) agent.getService(IMetadataRepositoryManager.SERVICE_NAME); |
| URI[] repositories = metadataRepositories == null ? repoManager.getKnownRepositories(IRepositoryManager.REPOSITORIES_ALL) : metadataRepositories; |
| |
| HashMap<String, IMetadataRepository> repos = new HashMap<>(); |
| SubMonitor sub = SubMonitor.convert(monitor, repositories.length * 100); |
| |
| // Clear out the list of remembered artifact repositories |
| referencedArtifactRepositories = new HashMap<>(); |
| for (int i = 0; i < repositories.length; i++) { |
| if (sub.isCanceled()) |
| throw new OperationCanceledException(); |
| loadMetadataRepository(repoManager, repositories[i], repos, shouldFollowReferences(), sub.newChild(100)); |
| } |
| Set<IMetadataRepository> set = new HashSet<>(); |
| set.addAll(repos.values()); |
| return set; |
| } |
| |
| private void loadMetadataRepository(IMetadataRepositoryManager manager, URI location, HashMap<String, IMetadataRepository> repos, boolean followMetadataRepoReferences, IProgressMonitor monitor) { |
| // if we've already processed this repo, don't do it again. This keeps us from getting |
| // caught up in circular references. |
| if (repos.containsKey(location.toString())) |
| return; |
| |
| SubMonitor sub = SubMonitor.convert(monitor, 1000); |
| // First load the repository itself. |
| IMetadataRepository repository; |
| try { |
| repository = manager.loadRepository(location, sub.newChild(500)); |
| } catch (ProvisionException e) { |
| // nothing more to do |
| return; |
| } |
| repos.put(location.toString(), repository); |
| Collection<IRepositoryReference> references = repository.getReferences(); |
| // We always load artifact repositories referenced by this repository. We might load |
| // metadata repositories |
| if (references.size() > 0) { |
| IArtifactRepositoryManager artifactManager = (IArtifactRepositoryManager) agent.getService(IArtifactRepositoryManager.SERVICE_NAME); |
| SubMonitor repoSubMon = SubMonitor.convert(sub.newChild(500), 100 * references.size()); |
| for (IRepositoryReference ref : references) { |
| try { |
| if (ref.getType() == IRepository.TYPE_METADATA && followMetadataRepoReferences && isEnabled(manager, ref)) { |
| loadMetadataRepository(manager, ref.getLocation(), repos, followMetadataRepoReferences, repoSubMon.newChild(100)); |
| } else if (ref.getType() == IRepository.TYPE_ARTIFACT) { |
| // We want to remember all enabled artifact repository locations. |
| if (isEnabled(artifactManager, ref)) |
| referencedArtifactRepositories.put(ref.getLocation().toString(), ref.getLocation()); |
| } |
| } catch (IllegalArgumentException e) { |
| // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=311338 |
| // ignore invalid location and keep going |
| } |
| } |
| } else { |
| sub.done(); |
| } |
| |
| } |
| |
| // If the manager knows about the repo, consider its enablement state in the manager. |
| // If the manager does not know about the repo, consider the reference enablement state |
| @SuppressWarnings("rawtypes") |
| private boolean isEnabled(IRepositoryManager manager, IRepositoryReference reference) { |
| return (manager.contains(reference.getLocation()) && manager.isEnabled(reference.getLocation())) || ((!manager.contains(reference.getLocation())) && ((reference.getOptions() & IRepository.ENABLED) == IRepository.ENABLED)); |
| } |
| |
| private boolean shouldFollowReferences() { |
| return Boolean.parseBoolean(getProperty(FOLLOW_REPOSITORY_REFERENCES)); |
| } |
| |
| private boolean shouldFollowArtifactReferences() { |
| return Boolean.parseBoolean(getProperty(FOLLOW_ARTIFACT_REPOSITORY_REFERENCES)); |
| } |
| |
| /** |
| * Returns a queryable that can be used to obtain any metadata (installable units) |
| * that are needed for the provisioning operation. |
| * |
| * The provisioning context has a distinct lifecycle, whereby the metadata |
| * and artifact repositories to be used are determined when the client retrieves |
| * retrieves the metadata queryable. Clients should not reset the list of |
| * metadata repository locations or artifact repository locations once the |
| * metadata queryable has been retrieved. |
| * |
| * @param monitor a progress monitor to be used when creating the queryable |
| * @return a queryable that can be used to query available metadata. |
| * |
| * @see #setMetadataRepositories(URI[]) |
| * @see #FOLLOW_REPOSITORY_REFERENCES |
| */ |
| public IQueryable<IInstallableUnit> getMetadata(IProgressMonitor monitor) { |
| return QueryUtil.compoundQueryable(getLoadedMetadataRepositories(monitor)); |
| } |
| |
| /** |
| * Returns the list of additional installable units that should be considered as |
| * available for installation by the planner. Returns an empty list if |
| * there are no extra installable units to consider. This method has no effect on the |
| * execution of the engine. |
| * |
| * @return The extra installable units that are available |
| */ |
| public List<IInstallableUnit> getExtraInstallableUnits() { |
| return extraIUs; |
| } |
| |
| /** |
| * Returns the properties that are defined in this context. Context properties can |
| * be used to influence the behavior of either the planner or engine. |
| * |
| * @return the defined provisioning context properties |
| */ |
| public Map<String, String> getProperties() { |
| return properties; |
| } |
| |
| /** |
| * Returns the value of the property with the given key, or <code>null</code> |
| * if no such property is defined |
| * @param key the property key |
| * @return the property value, or <code>null</code> |
| */ |
| public String getProperty(String key) { |
| return properties.get(key); |
| } |
| |
| /** |
| * Sets the artifact repositories to consult when performing an operation. |
| * <p> |
| * The provisioning context has a distinct lifecycle, whereby the metadata |
| * and artifact repositories to be used are determined when the client |
| * retrieves the metadata queryable. Clients should not reset the list of |
| * artifact repository locations once the metadata queryable has been retrieved. |
| * |
| * @param artifactRepositories the artifact repository locations |
| */ |
| public void setArtifactRepositories(URI... artifactRepositories) { |
| this.artifactRepositories = artifactRepositories; |
| } |
| |
| /** |
| * Sets the metadata repositories to consult when performing an operation. |
| * <p> |
| * The provisioning context has a distinct lifecycle, whereby the metadata |
| * and artifact repositories to be used are determined when the client |
| * retrieves the metadata queryable. Clients should not reset the list of |
| * metadata repository locations once the metadata queryable has been retrieved. |
| * @param metadataRepositories the metadata repository locations |
| */ |
| public void setMetadataRepositories(URI... metadataRepositories) { |
| this.metadataRepositories = metadataRepositories; |
| } |
| |
| /** |
| * Sets the list of additional installable units that should be considered as |
| * available for installation by the planner. This method has no effect on the |
| * execution of the engine. |
| * @param extraIUs the extra installable units |
| */ |
| public void setExtraInstallableUnits(List<IInstallableUnit> extraIUs) { |
| this.extraIUs.clear(); |
| //copy the list to prevent future client tampering |
| if (extraIUs != null) |
| this.extraIUs.addAll(extraIUs); |
| } |
| |
| /** |
| * Sets a property on this provisioning context. Context properties can |
| * be used to influence the behavior of either the planner or engine. |
| * @param key the property key |
| * @param value the property value |
| */ |
| public void setProperty(String key, String value) { |
| properties.put(key, value); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see java.lang.Object#toString() |
| */ |
| @Override |
| public String toString() { |
| StringBuffer buffer = new StringBuffer(); |
| buffer.append("{artifactRepos=" + DebugHelper.formatArray(null != artifactRepositories ? Arrays.asList(artifactRepositories) : null, true, false)); //$NON-NLS-1$ |
| buffer.append(", metadataRepos=" + DebugHelper.formatArray(null != metadataRepositories ? Arrays.asList(metadataRepositories) : null, true, false)); //$NON-NLS-1$ |
| buffer.append(", properties=" + getProperties() + "}"); //$NON-NLS-1$ //$NON-NLS-2$ |
| return buffer.toString(); |
| } |
| } |