| /* |
| * Copyright (c) 2014-2016 Eike Stepper (Berlin, Germany) 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: |
| * Eike Stepper - initial API and implementation |
| */ |
| package org.eclipse.oomph.p2.internal.core; |
| |
| import org.eclipse.oomph.util.CollectionUtil; |
| import org.eclipse.oomph.util.IOUtil; |
| import org.eclipse.oomph.util.OfflineMode; |
| import org.eclipse.oomph.util.PropertiesUtil; |
| import org.eclipse.oomph.util.ReflectUtil; |
| import org.eclipse.oomph.util.ReflectUtil.ReflectionException; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.OperationCanceledException; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.SubMonitor; |
| import org.eclipse.equinox.internal.p2.artifact.repository.Activator; |
| import org.eclipse.equinox.internal.p2.artifact.repository.ArtifactRepositoryManager; |
| import org.eclipse.equinox.internal.p2.artifact.repository.MirrorSelector; |
| import org.eclipse.equinox.internal.p2.artifact.repository.simple.SimpleArtifactRepository; |
| import org.eclipse.equinox.internal.p2.core.helpers.LogHelper; |
| import org.eclipse.equinox.internal.p2.core.helpers.OrderedProperties; |
| import org.eclipse.equinox.internal.p2.engine.CommitOperationEvent; |
| import org.eclipse.equinox.internal.p2.engine.RollbackOperationEvent; |
| import org.eclipse.equinox.internal.p2.metadata.repository.MetadataRepositoryManager; |
| import org.eclipse.equinox.internal.p2.repository.DownloadStatus; |
| import org.eclipse.equinox.internal.p2.repository.Transport; |
| import org.eclipse.equinox.internal.p2.repository.helpers.AbstractRepositoryManager; |
| import org.eclipse.equinox.internal.p2.repository.helpers.LocationProperties; |
| import org.eclipse.equinox.internal.provisional.p2.core.eventbus.IProvisioningEventBus; |
| import org.eclipse.equinox.internal.provisional.p2.core.eventbus.SynchronousProvisioningListener; |
| import org.eclipse.equinox.internal.provisional.p2.repository.RepositoryEvent; |
| import org.eclipse.equinox.p2.core.IAgentLocation; |
| 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.repository.IRepository; |
| import org.eclipse.osgi.util.NLS; |
| |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.lang.reflect.Method; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.text.NumberFormat; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.EventObject; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeMap; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import java.util.concurrent.atomic.AtomicLong; |
| |
| /** |
| * @author Eike Stepper |
| */ |
| @SuppressWarnings("restriction") |
| public class CachingRepositoryManager<T> |
| { |
| public static final String BOGUS_SCHEME = "bogus"; |
| |
| private static final Method METHOD_checkValidLocation = ReflectUtil.getMethod(AbstractRepositoryManager.class, "checkValidLocation", URI.class); |
| |
| private static final Method METHOD_enterLoad = ReflectUtil.getMethod(AbstractRepositoryManager.class, "enterLoad", URI.class, IProgressMonitor.class); |
| |
| private static final Method METHOD_basicGetRepository = ReflectUtil.getMethod(AbstractRepositoryManager.class, "basicGetRepository", URI.class); |
| |
| private static final Method METHOD_fail = ReflectUtil.getMethod(AbstractRepositoryManager.class, "fail", URI.class, int.class); |
| |
| private static final Method METHOD_addRepository1 = ReflectUtil.getMethod(AbstractRepositoryManager.class, "addRepository", URI.class, boolean.class, |
| boolean.class); |
| |
| private static final Method METHOD_loadIndexFile = ReflectUtil.getMethod(AbstractRepositoryManager.class, "loadIndexFile", URI.class, IProgressMonitor.class); |
| |
| private static final Method METHOD_getPreferredRepositorySearchOrder = ReflectUtil.getMethod(AbstractRepositoryManager.class, |
| "getPreferredRepositorySearchOrder", LocationProperties.class); |
| |
| private static final Method METHOD_getAllSuffixes = ReflectUtil.getMethod(AbstractRepositoryManager.class, "getAllSuffixes"); |
| |
| private static final Method METHOD_loadRepository = ReflectUtil.getMethod(AbstractRepositoryManager.class, "loadRepository", URI.class, String.class, |
| String.class, int.class, SubMonitor.class); |
| |
| private static final Method METHOD_addRepository2 = ReflectUtil.getMethod(AbstractRepositoryManager.class, "addRepository", IRepository.class, boolean.class, |
| String.class); |
| |
| private static final Method METHOD_removeRepository = ReflectUtil.getMethod(AbstractRepositoryManager.class, "removeRepository", URI.class, boolean.class); |
| |
| private static final Method METHOD_exitLoad = ReflectUtil.getMethod(AbstractRepositoryManager.class, "exitLoad", URI.class); |
| |
| private static final Method METHOD_broadcastChangeEvent = ReflectUtil.getMethod(AbstractRepositoryManager.class, "broadcastChangeEvent", URI.class, int.class, |
| int.class, boolean.class); |
| |
| private static final String PROPERTY_VERSION = "version"; |
| |
| private static final String PROP_BETTER_MIRROR_SELECTION = "oomph.p2.mirror"; |
| |
| private static boolean betterMirrorSelection; |
| |
| private final AbstractRepositoryManager<T> delegate; |
| |
| private final int repositoryType; |
| |
| private final CachingTransport transport; |
| |
| public CachingRepositoryManager(AbstractRepositoryManager<T> delegate, int repositoryType, CachingTransport transport) |
| { |
| this.delegate = delegate; |
| |
| IAgentLocation agentLocation = ReflectUtil.getValue("agentLocation", delegate); |
| if (agentLocation != null) |
| { |
| URI rootLocation = agentLocation.getRootLocation(); |
| if (rootLocation != null) |
| { |
| if (!IOUtil.canWriteFolder(new File(rootLocation.getPath()))) |
| { |
| ReflectUtil.setValue("agentLocation", delegate, null); |
| } |
| } |
| } |
| |
| this.repositoryType = repositoryType; |
| |
| if (transport == null) |
| { |
| Object t = delegate.getAgent().getService(Transport.SERVICE_NAME); |
| if (t instanceof CachingTransport) |
| { |
| transport = (CachingTransport)t; |
| } |
| } |
| |
| this.transport = transport; |
| } |
| |
| public CachingTransport getTransport() |
| { |
| return transport; |
| } |
| |
| public 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; |
| |
| CachingTransport.startLoadingRepository(location); |
| |
| try |
| { |
| enterLoad(location, sub.newChild(5)); |
| |
| try |
| { |
| result = basicGetRepository(location); |
| if (result != null) |
| { |
| return result; |
| } |
| |
| // 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[] allSuffixes = getAllSuffixes(); |
| String[] suffixes = sortSuffixes(allSuffixes, preferredOrder); |
| |
| sub = SubMonitor.convert(sub, NLS.bind("Adding repository {0}", 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) |
| { |
| if (!(e.getStatus().getException() instanceof FileNotFoundException)) |
| { |
| failure = e; |
| break; |
| } |
| } |
| |
| if (result != null) |
| { |
| addRepository(result, false, suffixes[i]); |
| cacheIndexFile(location, 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(delegate.getRepositoryProperty(location, IRepository.PROP_SYSTEM))) |
| { |
| delegate.removeRepository(location); |
| } |
| |
| if (failure != null) |
| { |
| throw failure; |
| } |
| |
| fail(location, ProvisionException.REPOSITORY_NOT_FOUND); |
| } |
| } |
| finally |
| { |
| exitLoad(location); |
| } |
| } |
| finally |
| { |
| CachingTransport.stopLoadingRepository(); |
| } |
| |
| // Broadcast the add event after releasing lock. |
| if (added) |
| { |
| broadcastChangeEvent(location, repositoryType, RepositoryEvent.ADDED, true); |
| } |
| |
| return result; |
| } |
| |
| private File getCachedIndexFile(URI location) |
| { |
| try |
| { |
| String path = location.toString(); |
| if (!path.endsWith("/")) |
| { |
| path += "/"; |
| } |
| |
| return transport.getCacheFile(new URI(path + "p2.index")); |
| } |
| catch (URISyntaxException ex) |
| { |
| // Can't happen. |
| throw new RuntimeException(ex); |
| } |
| } |
| |
| private void cacheIndexFile(URI location, String suffix) |
| { |
| if ("file".equals(location.getScheme())) |
| { |
| return; |
| } |
| |
| File cachedIndexFile = getCachedIndexFile(location); |
| |
| Map<String, String> properties = PropertiesUtil.getProperties(cachedIndexFile); |
| if (!properties.containsKey(PROPERTY_VERSION)) |
| { |
| properties.put(PROPERTY_VERSION, "1"); |
| } |
| |
| if (repositoryType == IRepository.TYPE_METADATA) |
| { |
| properties.put("metadata.repository.factory.order", suffix); |
| } |
| else |
| { |
| properties.put("artifact.repository.factory.order", suffix); |
| } |
| |
| // Cleanup; can be removed at some point in the future... |
| properties.remove("generated"); |
| |
| PropertiesUtil.saveProperties(cachedIndexFile, properties, false); |
| } |
| |
| private URI checkValidLocation(URI location) |
| { |
| return (URI)ReflectUtil.invokeMethod(METHOD_checkValidLocation, delegate, location); |
| } |
| |
| private void enterLoad(URI location, IProgressMonitor monitor) |
| { |
| ReflectUtil.invokeMethod(METHOD_enterLoad, delegate, location, monitor); |
| } |
| |
| @SuppressWarnings("unchecked") |
| protected IRepository<T> basicGetRepository(URI location) |
| { |
| return (IRepository<T>)ReflectUtil.invokeMethod(METHOD_basicGetRepository, delegate, location); |
| } |
| |
| // private boolean checkNotFound(URI location) |
| // { |
| // return (Boolean)ReflectUtil.invokeMethod(METHOD_checkNotFound, delegate, location); |
| // } |
| // |
| // private void rememberNotFound(URI location) |
| // { |
| // ReflectUtil.invokeMethod(METHOD_rememberNotFound, delegate, location); |
| // } |
| |
| private void fail(URI location, int code) throws ProvisionException |
| { |
| try |
| { |
| ReflectUtil.invokeMethod(METHOD_fail, delegate, location, code); |
| } |
| catch (ReflectionException ex) |
| { |
| Throwable cause = ex.getCause(); |
| if (cause instanceof ProvisionException) |
| { |
| throw (ProvisionException)cause; |
| } |
| |
| throw ex; |
| } |
| } |
| |
| private boolean addRepository(URI location, boolean isEnabled, boolean signalAdd) |
| { |
| return (Boolean)ReflectUtil.invokeMethod(METHOD_addRepository1, delegate, location, isEnabled, signalAdd); |
| } |
| |
| private LocationProperties loadIndexFile(URI location, IProgressMonitor monitor) |
| { |
| return (LocationProperties)ReflectUtil.invokeMethod(METHOD_loadIndexFile, delegate, location, monitor); |
| } |
| |
| protected String[] getPreferredRepositorySearchOrder(LocationProperties properties) |
| { |
| return (String[])ReflectUtil.invokeMethod(METHOD_getPreferredRepositorySearchOrder, delegate, properties); |
| } |
| |
| protected String[] getAllSuffixes() |
| { |
| return (String[])ReflectUtil.invokeMethod(METHOD_getAllSuffixes, delegate); |
| } |
| |
| private String[] sortSuffixes(String[] allSuffixes, String[] preferredOrder) |
| { |
| List<String> suffixes = new ArrayList<String>(Arrays.asList(allSuffixes)); |
| |
| for (int i = preferredOrder.length - 1; i >= 0; --i) |
| { |
| String suffix = preferredOrder[i].trim(); |
| if (!LocationProperties.END.equals(suffix)) |
| { |
| suffixes.remove(suffix); |
| suffixes.add(0, suffix); |
| } |
| } |
| |
| return suffixes.toArray(new String[suffixes.size()]); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private IRepository<T> loadRepository(URI location, String suffix, String type, int flags, SubMonitor monitor) throws ProvisionException |
| { |
| try |
| { |
| return (IRepository<T>)ReflectUtil.invokeMethod(METHOD_loadRepository, delegate, location, suffix, type, flags, monitor); |
| } |
| catch (ReflectionException ex) |
| { |
| Throwable cause = ex.getCause(); |
| if (cause instanceof ProvisionException) |
| { |
| throw (ProvisionException)cause; |
| } |
| |
| throw ex; |
| } |
| } |
| |
| protected void addRepository(IRepository<T> repository, boolean signalAdd, String suffix) |
| { |
| ReflectUtil.invokeMethod(METHOD_addRepository2, delegate, repository, signalAdd, suffix); |
| } |
| |
| private boolean removeRepository(URI toRemove, boolean signalRemove) |
| { |
| return (Boolean)ReflectUtil.invokeMethod(METHOD_removeRepository, delegate, toRemove, signalRemove); |
| } |
| |
| private void exitLoad(URI location) |
| { |
| ReflectUtil.invokeMethod(METHOD_exitLoad, delegate, location); |
| } |
| |
| private void broadcastChangeEvent(URI location, int repositoryType, int kind, boolean isEnabled) |
| { |
| ReflectUtil.invokeMethod(METHOD_broadcastChangeEvent, delegate, location, repositoryType, kind, isEnabled); |
| } |
| |
| public static boolean isBetterMirrorSelection() |
| { |
| return betterMirrorSelection; |
| } |
| |
| public static boolean enableBetterMirrorSelection() |
| { |
| boolean originalBetterMirrorSelection = betterMirrorSelection; |
| setBetterMirrorSelection(!"false".equals(PropertiesUtil.getProperty(PROP_BETTER_MIRROR_SELECTION))); |
| return originalBetterMirrorSelection; |
| } |
| |
| public static void setBetterMirrorSelection(boolean betterMirrorSelection) |
| { |
| CachingRepositoryManager.betterMirrorSelection = betterMirrorSelection; |
| } |
| |
| /** |
| * @author Eike Stepper |
| */ |
| public static class Metadata extends MetadataRepositoryManager |
| { |
| private final CachingRepositoryManager<IInstallableUnit> loader; |
| |
| public Metadata(IProvisioningAgent agent, CachingTransport transport) |
| { |
| super(agent); |
| loader = new CachingRepositoryManager<IInstallableUnit>(this, IRepository.TYPE_METADATA, transport); |
| } |
| |
| @Override |
| protected IRepository<IInstallableUnit> loadRepository(URI location, IProgressMonitor monitor, String type, int flags) throws ProvisionException |
| { |
| return loader.loadRepository(location, monitor, type, flags); |
| } |
| |
| @Override |
| public URI[] getKnownRepositories(int flags) |
| { |
| return filter(super.getKnownRepositories(flags)); |
| } |
| |
| /** |
| * Work-around for bug 483286. |
| */ |
| @Override |
| public void flushCache() |
| { |
| synchronized (repositoryLock) |
| { |
| if (repositories != null) |
| { |
| super.flushCache(); |
| } |
| } |
| } |
| |
| static URI[] filter(URI[] uris) |
| { |
| List<URI> result = new ArrayList<URI>(uris.length); |
| for (URI uri : uris) |
| { |
| try |
| { |
| if (!"file".equalsIgnoreCase(uri.getScheme()) || new File(uri).isDirectory()) |
| { |
| result.add(uri); |
| } |
| } |
| catch (IllegalArgumentException ex) |
| { |
| //$FALL-THROUGH$ |
| } |
| } |
| |
| return result.toArray(new URI[result.size()]); |
| } |
| } |
| |
| /** |
| * @author Eike Stepper |
| */ |
| public static class Artifact extends ArtifactRepositoryManager |
| { |
| private static final String GLOBAL_MAX_THREADS = Activator.getContext().getProperty(SimpleArtifactRepository.PROP_MAX_THREADS); |
| |
| private final CachingRepositoryManager<IArtifactKey> loader; |
| |
| public Artifact(IProvisioningAgent agent, CachingTransport transport) |
| { |
| super(agent); |
| loader = new CachingRepositoryManager<IArtifactKey>(this, IRepository.TYPE_ARTIFACT, transport); |
| } |
| |
| @Override |
| protected IRepository<IArtifactKey> loadRepository(URI location, IProgressMonitor monitor, String type, int flags) throws ProvisionException |
| { |
| // Inspect the repository if better mirror selection is enabled, |
| // the repository is simple |
| // and the repository not modifiable, i.e., if it's not a local file-based repository. |
| IRepository<IArtifactKey> result = loader.loadRepository(location, monitor, type, flags); |
| if (isBetterMirrorSelection() && result instanceof SimpleArtifactRepository && !result.isModifiable()) |
| { |
| // There should always be an event bus. |
| IProvisioningEventBus eventBus = (IProvisioningEventBus)getAgent().getService(IProvisioningEventBus.SERVICE_NAME); |
| if (eventBus != null) |
| { |
| // Create our improved mirror selector and reflectively set it to be the one used by this repository. |
| final BetterMirrorSelector mirrorSelector = new BetterMirrorSelector(result, loader.getTransport(), eventBus); |
| ReflectUtil.setValue("mirrors", result, mirrorSelector); |
| |
| // Because of the poor implementation in org.eclipse.equinox.internal.p2.artifact.repository.simple.SimpleArtifactRepository.getMaximumThreads() |
| // which was reported in https://bugs.eclipse.org/bugs/show_bug.cgi?id=471861 but is not yet fixed, |
| // we work the around the problem by setting the global PROP_MAX_THREADS into the repository properties. |
| if (GLOBAL_MAX_THREADS != null) |
| { |
| Map<String, String> properties = ReflectUtil.getValue("properties", result); |
| Map<String, String> specializedProperties = new OrderedProperties() |
| { |
| @Override |
| public Set<java.util.Map.Entry<String, String>> entrySet() |
| { |
| if (!OfflineMode.isEnabled()) |
| { |
| // If the request for the entry set occurs when p2 is determining the maximum number of threads allowed by the repository... |
| StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); |
| if (stackTrace.length > 5 && "getMaximumThreads".equals(stackTrace[5].getMethodName())) |
| { |
| // If the repository itself doesn't restrict the maximum number of threads... |
| String respositoryMaxThreads = get(SimpleArtifactRepository.PROP_MAX_THREADS); |
| if (respositoryMaxThreads == null) |
| { |
| // Initialize our specialized mirror selector. |
| mirrorSelector.initMirrorActivities(new NullProgressMonitor()); |
| |
| // If there is no list of mirrors, allow 10 threads, otherwise, allow 10 threads per mirror or the global maximum, whichever is less. |
| // Save that value as if it were a property in the repository. |
| BetterMirrorSelector.MirrorActivity[] mirrorActivities = mirrorSelector.mirrorActivities; |
| int maxThreads = mirrorActivities.length <= 1 ? 10 : Math.min(mirrorActivities.length * 10, Integer.parseInt(GLOBAL_MAX_THREADS)); |
| put(SimpleArtifactRepository.PROP_MAX_THREADS, Integer.toString(maxThreads)); |
| } |
| } |
| } |
| |
| return super.entrySet(); |
| } |
| }; |
| |
| // Copy over the properties and insert our specialized map reflective into the field of the base class. |
| specializedProperties.putAll(properties); |
| ReflectUtil.setValue("properties", result, specializedProperties); |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| @Override |
| public URI[] getKnownRepositories(int flags) |
| { |
| return Metadata.filter(super.getKnownRepositories(flags)); |
| } |
| |
| /** |
| * Work-around for bug 483286. |
| */ |
| @Override |
| public void flushCache() |
| { |
| synchronized (repositoryLock) |
| { |
| if (repositories != null) |
| { |
| super.flushCache(); |
| } |
| } |
| } |
| |
| /** |
| * @author Eike Stepper |
| * @author Ed Merks |
| */ |
| static final class BetterMirrorSelector extends MirrorSelector implements SynchronousProvisioningListener |
| { |
| /** |
| * The main artifact repository. |
| */ |
| private final URI repositoryURI; |
| |
| /** |
| * The event bus to which this selector listens. |
| */ |
| private final IProvisioningEventBus eventBus; |
| |
| /** |
| * A map from mirror URI to the artifact activity for that mirror. |
| */ |
| private final Map<URI, ArtifactActivity> artifactActivities = Collections.synchronizedMap(new HashMap<URI, ArtifactActivity>()); |
| |
| /** |
| * The phase in which we are using mirrors. |
| * During the initial phase, each mirror is probed to measure performance. |
| * Subsequently only the faster mirrors are used. |
| */ |
| private int phase = 1; |
| |
| /** |
| * The per-mirror activities being managed. |
| */ |
| private MirrorActivity[] mirrorActivities; |
| |
| public BetterMirrorSelector(IRepository<?> repository, Transport transport, IProvisioningEventBus eventBus) |
| { |
| super(repository, transport); |
| this.eventBus = eventBus; |
| repositoryURI = getBaseURI(); |
| } |
| |
| public void notify(EventObject event) |
| { |
| // If we have mirror activities. |
| if (mirrorActivities != null) |
| { |
| // If it's a download event. |
| if (event instanceof DownloadArtifactEvent) |
| { |
| // Get the corresponding artifact activity for this event, if there is one.. |
| DownloadArtifactEvent downloadArtifactEvent = (DownloadArtifactEvent)event; |
| URI artifactURI = downloadArtifactEvent.getArtifactURI(); |
| ArtifactActivity artifactActivity = artifactActivities.get(artifactURI); |
| if (artifactActivity != null) |
| { |
| // Get the corresponding overall mirror activity. |
| MirrorActivity mirrorActivity = artifactActivity.getMirrorActivity(); |
| if (downloadArtifactEvent.isCompleted()) |
| { |
| // If the download is completed, reduce the usage count of this mirror. |
| mirrorActivity.decrementUsage(); |
| |
| // Determine if it was successful or not. |
| IStatus status = downloadArtifactEvent.getStatus(); |
| if (status.isOK()) |
| { |
| // Increment the success count of this mirror. |
| mirrorActivity.incrementSuccess(); |
| |
| // Record the overall end-to-end speed based or course on the overall time for the download the start and complete along with the number of |
| // bytes. |
| DownloadStatus downloadStatus = (DownloadStatus)status; |
| long fileSize = downloadStatus.getFileSize(); |
| artifactActivity.setSize(fileSize); |
| mirrorActivity.setSpeed(artifactActivity.getSpeed()); |
| } |
| else |
| { |
| // There was a failure. |
| mirrorActivity.incrementFailure(); |
| |
| // So no bytes were successfully downloaded. |
| artifactActivity.setSize(0); |
| |
| // This will reduce the average speed of the mirror in half, quickly dropping it from consideration for use. |
| mirrorActivity.setSpeed(0); |
| } |
| } |
| else |
| { |
| // If the download is started, increment the usage count of the mirror. |
| mirrorActivity.incrementUsage(); |
| |
| // Record the point in time at which this activity started. |
| artifactActivity.setStart(); |
| } |
| } |
| } |
| else if (event instanceof CommitOperationEvent || event instanceof RollbackOperationEvent) |
| { |
| // If we complete or we roll back, we remove this as a listener. |
| if (eventBus != null) |
| { |
| eventBus.removeListener(this); |
| } |
| |
| // And reset our state to the intial state for subsequent reuse. |
| mirrorActivities = null; |
| artifactActivities.clear(); |
| phase = 1; |
| } |
| } |
| } |
| |
| @Override |
| public synchronized URI getMirrorLocation(URI inputLocation, IProgressMonitor monitor) |
| { |
| // If we don't know the repository URI, we can't request mirrors. |
| Assert.isNotNull(inputLocation); |
| if (repositoryURI == null) |
| { |
| return inputLocation; |
| } |
| |
| // Determine the relative location of the download with respect to the repository. |
| URI relativeLocation = repositoryURI.relativize(inputLocation); |
| if (relativeLocation == null || relativeLocation.isAbsolute()) |
| { |
| // If it's not relative, we just use the given URI. |
| return inputLocation; |
| } |
| |
| // Initialize the mirrors with a mirror request to populate the list. |
| initMirrorActivities(monitor); |
| |
| // Select the mirror to be used for the artifact activity. |
| MirrorActivity selectedMirror = selectMirror(); |
| if (selectedMirror == null) |
| { |
| // If there isn't one, we must use the given input. |
| return inputLocation; |
| } |
| |
| String location = selectedMirror.getLocation(); |
| try |
| { |
| // Determine the location within the mirror, create a new artifact activity for it, and see if there was previous such an activity. |
| URI artifactURI = new URI(location + relativeLocation.getPath()); |
| ArtifactActivity newActivity = new ArtifactActivity(selectedMirror); |
| ArtifactActivity previousActivity = artifactActivities.put(artifactURI, newActivity); |
| if (previousActivity != null) |
| { |
| if (!newActivity.retry(previousActivity)) |
| { |
| // Return a bogus URI that will refuse to try this artifactURI from this mirror again. |
| return new URI(BOGUS_SCHEME + ":" + artifactURI); |
| } |
| |
| // We don't expect to have repeated requests for the same artifact. |
| // But this does happen if it's a .pack.gz produced by a version of Java newer than the version of Java being used by this process. |
| monitor.subTask("Repeated attempts to download " + artifactURI + " probably because it can't be processed"); |
| } |
| |
| // Use this location in the mirror instead of the original URI. |
| return artifactURI; |
| } |
| catch (URISyntaxException e) |
| { |
| log("Unable to make location " + inputLocation + " relative to mirror " + location, e); |
| } |
| |
| // If all else fails, we must use the original URI. |
| return inputLocation; |
| } |
| |
| private MirrorActivity selectMirror() |
| { |
| // If we don't have mirrors, we can't select one. |
| if (mirrorActivities.length == 0) |
| { |
| return null; |
| } |
| |
| if (phase == 1) |
| { |
| // During phase one, we try to probe each mirror once to measure speed. |
| for (MirrorActivity mirrorActivity : mirrorActivities) |
| { |
| if (!mirrorActivity.isProbed()) |
| { |
| mirrorActivity.setProbed(); |
| return mirrorActivity; |
| } |
| } |
| |
| // Once all mirrors are probed, we enter phase two. |
| phase = 2; |
| } |
| |
| // We sort the mirrors based on performance, favoring faster mirrors of course. |
| sort(); |
| |
| for (MirrorActivity mirrorActivity : mirrorActivities) |
| { |
| // We try to limit the number of simultaneous activities per mirror to avoid overloading any one mirror. |
| int MAX_ACTIVITY_PER_HOST = 4; |
| int usages = mirrorActivity.getUsages(); |
| if (usages < MAX_ACTIVITY_PER_HOST) |
| { |
| // Return the fasted one that's not so busy. |
| return mirrorActivity; |
| } |
| } |
| |
| // Find the least used mirror in the first half of the available mirrors. |
| MirrorActivity leastUsedMirror = null; |
| for (MirrorActivity mirrorActivity : mirrorActivities) |
| { |
| int usages = mirrorActivity.getUsages(); |
| if (leastUsedMirror == null || leastUsedMirror.getUsages() > usages) |
| { |
| leastUsedMirror = mirrorActivity; |
| } |
| } |
| |
| return leastUsedMirror; |
| } |
| |
| public List<String> getStats() |
| { |
| // Compute statistics for the mirror speeds. |
| List<String> result = new ArrayList<String>(); |
| if (mirrorActivities != null) |
| { |
| // We'll create new mirror activities to summarize the result. |
| MirrorActivity[] summaryMirrorActivities = new MirrorActivity[mirrorActivities.length]; |
| |
| for (int i = 0; i < mirrorActivities.length; ++i) |
| { |
| MirrorActivity mirrorActivity = mirrorActivities[i]; |
| MirrorActivity summaryMirrorActivity = summaryMirrorActivities[i] = new MirrorActivity(mirrorActivity.location); |
| for (int j = 0; j < mirrorActivity.getFailures(); ++j) |
| { |
| // Record the number of failures. |
| summaryMirrorActivity.incrementFailure(); |
| } |
| |
| // Compute the total number of bytes and the total time taken to download them for all the artifact activities. |
| long totalSize = 0; |
| long totalDuration = 0; |
| for (ArtifactActivity artifactActivity : artifactActivities.values()) |
| { |
| if (artifactActivity.getMirrorActivity() == mirrorActivity) |
| { |
| summaryMirrorActivity.incrementSuccess(); |
| totalSize += artifactActivity.getSize(); |
| totalDuration += artifactActivity.getDuration(); |
| } |
| } |
| |
| if (totalSize != 0) |
| { |
| // Record this as the overall result of the mirror activity. |
| summaryMirrorActivity.setSpeed(1000 * totalSize / totalDuration); |
| } |
| } |
| |
| // Replace with the summary. |
| mirrorActivities = summaryMirrorActivities; |
| |
| // Sort them again. |
| sort(); |
| |
| // Add only the ones that actually were used to the final result. |
| for (MirrorActivity mirrorActivity : mirrorActivities) |
| { |
| if (mirrorActivity.getSuccesses() + mirrorActivity.getFailures() > 0) |
| { |
| result.add(getStats(mirrorActivity)); |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| private String getStats(MirrorActivity mirrorActivity) |
| { |
| // Compute a nice string representation for the mirror activity. |
| NumberFormat numberFormat = NumberFormat.getInstance(Locale.US); |
| int successes = mirrorActivity.getSuccesses(); |
| int failures = mirrorActivity.getFailures(); |
| String message = "Mirrored " + successes + " artifacts from " + mirrorActivity.getLocation() + " at " |
| + numberFormat.format(mirrorActivity.getSpeed() / 1000) + "kb/s"; |
| if (failures > 0) |
| { |
| message += " with " + failures + " failures"; |
| } |
| |
| return message; |
| } |
| |
| private void sort() |
| { |
| // Compute a sorted map from the speed to the mirror activity with that speed. |
| Map<Long, Set<MirrorActivity>> sortedMap = new TreeMap<Long, Set<MirrorActivity>>(); |
| for (MirrorActivity mirrorActivity : mirrorActivities) |
| { |
| long value = mirrorActivity.getSpeed(); |
| CollectionUtil.add(sortedMap, value, mirrorActivity); |
| } |
| |
| // Flatten out the values of the map back into the array. |
| int index = mirrorActivities.length; |
| for (Set<MirrorActivity> acitivities : sortedMap.values()) |
| { |
| for (MirrorActivity mirrorActivity : acitivities) |
| { |
| mirrorActivities[--index] = mirrorActivity; |
| } |
| } |
| } |
| |
| private void initMirrorActivities(IProgressMonitor monitor) |
| { |
| // Do this only once. |
| if (mirrorActivities == null) |
| { |
| // Delete to p2's implementation. |
| Method method = ReflectUtil.getMethod(this, "initMirrors", IProgressMonitor.class); |
| MirrorInfo[] mirrors = (MirrorInfo[])ReflectUtil.invokeMethod(method, this, monitor); |
| if (mirrors == null) |
| { |
| mirrors = new MirrorInfo[0]; |
| } |
| |
| // Use only the first two thirds of the list. |
| int limit = (mirrors.length + 2) / 3; |
| if (limit == 0 && repositoryURI != null) |
| { |
| // Even if there are no mirrors treat it as a single mirror so that activities and performance can always be monitored. |
| String locationString = repositoryURI.toString(); |
| if (!locationString.endsWith("/")) |
| { |
| locationString += Character.toString('/'); |
| } |
| |
| mirrorActivities = new MirrorActivity[] { new MirrorActivity(locationString) }; |
| } |
| else |
| { |
| mirrorActivities = new MirrorActivity[limit]; |
| for (int i = 0; i < limit; i++) |
| { |
| // Convert the mirror info to our mirror activity representation. |
| MirrorInfo mirrorInfo = mirrors[i]; |
| String locationString = getLocationString(mirrorInfo); |
| MirrorActivity mirrorActivity = new MirrorActivity(locationString); |
| mirrorActivities[i] = mirrorActivity; |
| } |
| } |
| |
| // Do ourselves as a listener. |
| if (eventBus != null) |
| { |
| eventBus.addListener(this); |
| } |
| } |
| } |
| |
| private URI getBaseURI() |
| { |
| return ReflectUtil.getValue("baseURI", this); |
| } |
| |
| private static String getLocationString(MirrorInfo mirrorInfo) |
| { |
| return ReflectUtil.getValue("locationString", mirrorInfo); |
| } |
| |
| private static void log(String message, Throwable exception) |
| { |
| LogHelper.log(new Status(IStatus.ERROR, Activator.ID, message, exception)); |
| } |
| |
| /** |
| * @author Ed Merks |
| */ |
| private static class ArtifactActivity |
| { |
| private final MirrorActivity mirrorActivity; |
| |
| private long start; |
| |
| private long end; |
| |
| private long size; |
| |
| private int retryCount; |
| |
| public ArtifactActivity(MirrorActivity mirrorActivity) |
| { |
| this.mirrorActivity = mirrorActivity; |
| } |
| |
| public MirrorActivity getMirrorActivity() |
| { |
| return mirrorActivity; |
| } |
| |
| public void setStart() |
| { |
| this.start = System.currentTimeMillis(); |
| } |
| |
| public void setSize(long size) |
| { |
| end = System.currentTimeMillis(); |
| this.size = size; |
| } |
| |
| public long getDuration() |
| { |
| return end - start; |
| } |
| |
| public long getSpeed() |
| { |
| return size * 1000 / (end - start); |
| } |
| |
| public long getSize() |
| { |
| return size; |
| } |
| |
| public boolean retry(ArtifactActivity previousArtifactActivity) |
| { |
| if (previousArtifactActivity != null) |
| { |
| retryCount = previousArtifactActivity.retryCount + 1; |
| } |
| |
| return retryCount < 3; |
| } |
| |
| @Override |
| public String toString() |
| { |
| return "ArtifactActivity [start=" + start + ", end=" + end + ", size=" + size + ", duration=" + getDuration() + ", speed=" + getSpeed() + "]"; |
| } |
| } |
| |
| /** |
| * @author Ed Merks |
| */ |
| private static class MirrorActivity |
| { |
| private final String location; |
| |
| private final AtomicInteger usages = new AtomicInteger(); |
| |
| private final AtomicInteger successes = new AtomicInteger(); |
| |
| private final AtomicInteger failures = new AtomicInteger(); |
| |
| private final AtomicLong speed = new AtomicLong(); |
| |
| private boolean probed; |
| |
| public MirrorActivity(String location) |
| { |
| this.location = location; |
| } |
| |
| public String getLocation() |
| { |
| return location; |
| } |
| |
| public boolean isProbed() |
| { |
| return probed; |
| } |
| |
| public void setProbed() |
| { |
| probed = true; |
| } |
| |
| public int getUsages() |
| { |
| return usages.get(); |
| } |
| |
| public void incrementUsage() |
| { |
| usages.incrementAndGet(); |
| } |
| |
| public void decrementUsage() |
| { |
| usages.decrementAndGet(); |
| } |
| |
| public int getSuccesses() |
| { |
| return successes.get(); |
| } |
| |
| public void incrementSuccess() |
| { |
| successes.incrementAndGet(); |
| } |
| |
| public int getFailures() |
| { |
| return failures.get(); |
| } |
| |
| public void incrementFailure() |
| { |
| failures.incrementAndGet(); |
| } |
| |
| public synchronized long getSpeed() |
| { |
| return speed.get(); |
| } |
| |
| public synchronized void setSpeed(long speed) |
| { |
| long currentSpeed = this.speed.get(); |
| if (currentSpeed == 0) |
| { |
| currentSpeed = speed; |
| } |
| else |
| { |
| currentSpeed = (currentSpeed + speed) / 2; |
| } |
| |
| this.speed.set(currentSpeed); |
| } |
| |
| @Override |
| public String toString() |
| { |
| return "MirrorActivity [location=" + location + ", usages=" + usages.get() + ", successes=" + successes.get() + ", failures=" + failures.get() |
| + ", speed=" + speed.get() + ", probed=" + probed + "]"; |
| } |
| } |
| } |
| } |
| } |