/******************************************************************************* * 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(); } }