| /******************************************************************************* |
| * 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 |
| * EclipseSource - ongoing development |
| *******************************************************************************/ |
| package org.eclipse.equinox.internal.p2.metadata.repository; |
| |
| import java.io.*; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.util.*; |
| import java.util.jar.JarEntry; |
| import java.util.jar.JarOutputStream; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.equinox.internal.p2.core.helpers.LogHelper; |
| import org.eclipse.equinox.internal.p2.persistence.CompositeRepositoryIO; |
| import org.eclipse.equinox.internal.p2.persistence.CompositeRepositoryState; |
| import org.eclipse.equinox.p2.core.*; |
| import org.eclipse.equinox.p2.metadata.IInstallableUnit; |
| import org.eclipse.equinox.p2.metadata.index.IIndex; |
| import org.eclipse.equinox.p2.metadata.index.IIndexProvider; |
| import org.eclipse.equinox.p2.query.*; |
| import org.eclipse.equinox.p2.repository.*; |
| import org.eclipse.equinox.p2.repository.metadata.IMetadataRepository; |
| import org.eclipse.equinox.p2.repository.metadata.IMetadataRepositoryManager; |
| import org.eclipse.equinox.p2.repository.metadata.spi.AbstractMetadataRepository; |
| import org.eclipse.osgi.util.NLS; |
| |
| public class CompositeMetadataRepository extends AbstractMetadataRepository implements ICompositeRepository<IInstallableUnit>, IIndexProvider<IInstallableUnit> { |
| |
| static final public String REPOSITORY_TYPE = CompositeMetadataRepository.class.getName(); |
| static final public String PI_REPOSITORY_TYPE = "compositeMetadataRepository"; //$NON-NLS-1$ |
| static final public String PROP_ATOMIC_LOADING = "p2.atomic.composite.loading"; //$NON-NLS-1$ |
| |
| static final public boolean ATOMIC_LOADING_DEFAULT = Boolean.parseBoolean(Activator.getContext().getProperty("eclipse.p2.atomic.composite.loading.default")); //$NON-NLS-1$ |
| |
| static final private Integer REPOSITORY_VERSION = new Integer(1); |
| static final public String XML_EXTENSION = ".xml"; //$NON-NLS-1$ |
| static final private String JAR_EXTENSION = ".jar"; //$NON-NLS-1$ |
| |
| // keep a list of the child URIs. they can be absolute or relative. they may or may not point |
| // to a valid reachable repo |
| private List<URI> childrenURIs = new ArrayList<URI>(); |
| // keep a list of the repositories that we have successfully loaded |
| private List<IMetadataRepository> loadedRepos = new ArrayList<IMetadataRepository>(); |
| private IMetadataRepositoryManager manager; |
| private IPool<IInstallableUnit> iuPool = new WeakPool<IInstallableUnit>(); |
| |
| /** |
| * Create a Composite repository in memory. |
| * @return the repository or null if unable to create one |
| */ |
| public static CompositeMetadataRepository createMemoryComposite(IProvisioningAgent agent) { |
| if (agent == null) |
| return null; |
| IMetadataRepositoryManager repoManager = (IMetadataRepositoryManager) agent.getService(IMetadataRepositoryManager.SERVICE_NAME); |
| if (repoManager == null) |
| return null; |
| try { |
| //create a unique opaque URI |
| long time = System.currentTimeMillis(); |
| URI repositoryURI = new URI("memory:" + String.valueOf(time)); //$NON-NLS-1$ |
| while (repoManager.contains(repositoryURI)) |
| repositoryURI = new URI("memory:" + String.valueOf(++time)); //$NON-NLS-1$ |
| |
| CompositeMetadataRepository result = (CompositeMetadataRepository) repoManager.createRepository(repositoryURI, repositoryURI.toString(), IMetadataRepositoryManager.TYPE_COMPOSITE_REPOSITORY, null); |
| repoManager.removeRepository(repositoryURI); |
| return result; |
| } catch (ProvisionException e) { |
| // just return null |
| LogHelper.log(e); |
| } catch (URISyntaxException e) { |
| // just return null |
| } |
| return null; |
| } |
| |
| private IMetadataRepositoryManager getManager() { |
| return manager; |
| } |
| |
| private boolean isLocal() { |
| return "file".equalsIgnoreCase(getLocation().getScheme()); //$NON-NLS-1$ |
| } |
| |
| public boolean isModifiable() { |
| return isLocal(); |
| } |
| |
| /* |
| * This is only called by the parser when loading a repository. |
| */ |
| CompositeMetadataRepository(IMetadataRepositoryManager manager, CompositeRepositoryState state, IProgressMonitor monitor) throws ProvisionException { |
| super(manager.getAgent(), state.getName(), state.getType(), state.getVersion(), state.getLocation(), state.getDescription(), state.getProvider(), state.getProperties()); |
| this.manager = manager; |
| SubMonitor sub = SubMonitor.convert(monitor, 100 * state.getChildren().length); |
| List<URI> repositoriesToBeRemovedOnFailure = new ArrayList<URI>(); |
| boolean failOnChildFailure = shouldFailOnChildFailure(state); |
| for (URI child : state.getChildren()) |
| addChild(child, false, sub.newChild(100), failOnChildFailure, repositoriesToBeRemovedOnFailure); |
| |
| } |
| |
| CompositeMetadataRepository(IMetadataRepositoryManager manager, URI location, String name, Map<String, String> properties) { |
| super(manager.getAgent(), name == null ? (location != null ? location.toString() : "") : name, REPOSITORY_TYPE, REPOSITORY_VERSION.toString(), location, null, null, properties); //$NON-NLS-1$ |
| this.manager = manager; |
| //when creating a repository, we must ensure it exists on disk so a subsequent load will succeed |
| save(); |
| } |
| |
| /* |
| * Create and return a new repository state object which represents this repository. |
| * It will be used while persisting the repository to disk. |
| */ |
| public CompositeRepositoryState toState() { |
| CompositeRepositoryState result = new CompositeRepositoryState(); |
| result.setName(getName()); |
| result.setType(getType()); |
| result.setVersion(getVersion()); |
| result.setLocation(getLocation()); |
| result.setDescription(getDescription()); |
| result.setProvider(getProvider()); |
| result.setProperties(getProperties()); |
| // it is important to directly access the field so we have the relative URIs |
| result.setChildren(childrenURIs.toArray(new URI[childrenURIs.size()])); |
| return result; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.equinox.p2.query.IQueryable#query(org.eclipse.equinox.p2.query.IQuery, org.eclipse.core.runtime.IProgressMonitor) |
| */ |
| public IQueryResult<IInstallableUnit> query(IQuery<IInstallableUnit> query, IProgressMonitor monitor) { |
| if (monitor == null) |
| monitor = new NullProgressMonitor(); |
| try { |
| // Query all the all the repositories this composite repo contains |
| IQueryable<IInstallableUnit> queryable = QueryUtil.compoundQueryable(loadedRepos); |
| return queryable.query(query, monitor); |
| } finally { |
| if (monitor != null) |
| monitor.done(); |
| } |
| } |
| |
| //successfully loaded repo will be added to the list repositoriesToBeRemovedOnFailure if the list is not null and the repo wasn't previously loaded |
| private void addChild(URI childURI, boolean save, IProgressMonitor monitor, boolean propagateException, List<URI> repositoriesToBeRemovedOnFailure) throws ProvisionException { |
| SubMonitor sub = SubMonitor.convert(monitor); |
| URI absolute = URIUtil.makeAbsolute(childURI, getLocation()); |
| if (childrenURIs.contains(childURI) || childrenURIs.contains(absolute)) { |
| sub.done(); |
| return; |
| |
| } |
| // always add the URI to the list of child URIs (even if we can't load it later) |
| childrenURIs.add(childURI); |
| if (save) |
| save(); |
| try { |
| boolean currentLoaded = getManager().contains(absolute); |
| IMetadataRepository currentRepo = getManager().loadRepository(absolute, sub); |
| if (!currentLoaded) { |
| //set enabled to false so repositories do not polled twice |
| getManager().setEnabled(absolute, false); |
| //set repository to system to hide from users |
| getManager().setRepositoryProperty(absolute, IRepository.PROP_SYSTEM, String.valueOf(true)); |
| if (propagateException) |
| repositoriesToBeRemovedOnFailure.add(absolute); |
| } |
| currentRepo.compress(iuPool); // Share IUs across this CompositeMetadataRepository |
| // we successfully loaded the repo so remember it |
| loadedRepos.add(currentRepo); |
| |
| } catch (ProvisionException e) { |
| //repository failed to load. fall through |
| LogHelper.log(e); |
| if (propagateException) { |
| removeFromRepoManager(repositoriesToBeRemovedOnFailure); |
| String msg = NLS.bind(Messages.io_failedRead, getLocation()); |
| throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_FAILED_READ, msg, e)); |
| } |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.equinox.p2.repository.ICompositeRepository#addChild(java.net.URI) |
| */ |
| public void addChild(URI childURI) { |
| try { |
| addChild(childURI, true, null, false, null); |
| } catch (ProvisionException e) { |
| //already logged |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.equinox.p2.repository.ICompositeRepository#removeChild(java.net.URI) |
| */ |
| public void removeChild(URI childURI) { |
| boolean removed = childrenURIs.remove(childURI); |
| // if the child wasn't there make sure and try the other permutation |
| // (absolute/relative) to see if it really is in the list. |
| URI other = childURI.isAbsolute() ? URIUtil.makeRelative(childURI, getLocation()) : URIUtil.makeAbsolute(childURI, getLocation()); |
| if (!removed) |
| removed = childrenURIs.remove(other); |
| |
| if (removed) { |
| // we removed the child from the list so remove the associated repo object as well |
| IMetadataRepository found = null; |
| for (IMetadataRepository current : loadedRepos) { |
| URI repoLocation = current.getLocation(); |
| if (URIUtil.sameURI(childURI, repoLocation) || URIUtil.sameURI(other, repoLocation)) { |
| found = current; |
| break; |
| } |
| } |
| if (found != null) |
| loadedRepos.remove(found); |
| save(); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.equinox.p2.repository.ICompositeRepository#removeAllChildren() |
| */ |
| public void removeAllChildren() { |
| childrenURIs.clear(); |
| loadedRepos.clear(); |
| save(); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.equinox.p2.repository.metadata.spi.AbstractMetadataRepository#addInstallableUnits(java.util.Collection) |
| */ |
| @Override |
| public void addInstallableUnits(Collection<IInstallableUnit> installableUnits) { |
| throw new UnsupportedOperationException("Cannot add IUs to a composite repository"); //$NON-NLS-1$ |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.equinox.p2.repository.metadata.spi.AbstractMetadataRepository#removeAll() |
| */ |
| @Override |
| public synchronized void removeAll() { |
| throw new UnsupportedOperationException("Cannot remove IUs from a composite repository"); //$NON-NLS-1$ |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.equinox.p2.repository.metadata.spi.AbstractMetadataRepository#removeInstallableUnits(java.util.Collection) |
| */ |
| @Override |
| public boolean removeInstallableUnits(Collection<IInstallableUnit> installableUnits) { |
| throw new UnsupportedOperationException("Cannot remove IUs from a composite repository"); //$NON-NLS-1$ |
| } |
| |
| private static File getActualLocation(URI location, String extension) { |
| File spec = URIUtil.toFile(location); |
| String path = spec.getAbsolutePath(); |
| if (path.endsWith(CompositeMetadataRepositoryFactory.CONTENT_FILENAME + extension)) { |
| //todo this is the old code that doesn't look right |
| // return new File(spec + extension); |
| return spec; |
| } |
| if (path.endsWith("/")) //$NON-NLS-1$ |
| path += CompositeMetadataRepositoryFactory.CONTENT_FILENAME; |
| else |
| path += "/" + CompositeMetadataRepositoryFactory.CONTENT_FILENAME; //$NON-NLS-1$ |
| return new File(path + extension); |
| } |
| |
| public static File getActualLocation(URI location) { |
| return getActualLocation(location, XML_EXTENSION); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.equinox.p2.repository.metadata.spi.AbstractMetadataRepository#addReferences(java.util.Collection) |
| */ |
| public synchronized void addReferences(Collection<? extends IRepositoryReference> references) { |
| throw new UnsupportedOperationException("Cannot add References to a composite repository"); //$NON-NLS-1$ |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.equinox.p2.repository.metadata.IMetadataRepository#getReferences() |
| */ |
| public Collection<IRepositoryReference> getReferences() { |
| HashSet<IRepositoryReference> allRefs = new HashSet<IRepositoryReference>(); |
| for (IMetadataRepository child : loadedRepos) |
| allRefs.addAll(child.getReferences()); |
| return allRefs; |
| } |
| |
| // caller should be synchronized |
| private void save() { |
| if (!isModifiable()) |
| return; |
| File file = getActualLocation(getLocation()); |
| File jarFile = getActualLocation(getLocation(), JAR_EXTENSION); |
| boolean compress = "true".equalsIgnoreCase(getProperty(PROP_COMPRESSED)); //$NON-NLS-1$ |
| try { |
| OutputStream output = null; |
| if (!compress) { |
| if (jarFile.exists()) { |
| jarFile.delete(); |
| } |
| if (!file.exists()) { |
| if (!file.getParentFile().exists()) |
| file.getParentFile().mkdirs(); |
| file.createNewFile(); |
| } |
| output = new FileOutputStream(file); |
| } else { |
| if (file.exists()) { |
| file.delete(); |
| } |
| if (!jarFile.exists()) { |
| if (!jarFile.getParentFile().exists()) |
| jarFile.getParentFile().mkdirs(); |
| jarFile.createNewFile(); |
| } |
| JarEntry jarEntry = new JarEntry(file.getName()); |
| output = new JarOutputStream(new FileOutputStream(jarFile)); |
| ((JarOutputStream) output).putNextEntry(jarEntry); |
| } |
| super.setProperty(IRepository.PROP_TIMESTAMP, Long.toString(System.currentTimeMillis())); |
| new CompositeRepositoryIO().write(toState(), output, PI_REPOSITORY_TYPE); |
| } catch (IOException e) { |
| LogHelper.log(new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_FAILED_WRITE, NLS.bind(Messages.io_failedWrite, getLocation()), e)); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.equinox.p2.repository.ICompositeRepository#getChildren() |
| */ |
| public List<URI> getChildren() { |
| List<URI> result = new ArrayList<URI>(); |
| for (URI childURI : childrenURIs) |
| result.add(URIUtil.makeAbsolute(childURI, getLocation())); |
| return result; |
| } |
| |
| public static URI getActualLocationURI(URI base, String extension) { |
| if (extension == null) |
| extension = XML_EXTENSION; |
| return URIUtil.append(base, CompositeMetadataRepositoryFactory.CONTENT_FILENAME + extension); |
| } |
| |
| //TODO this should never be called. What do we do? |
| public void initialize(RepositoryState state) { |
| setName(state.Name); |
| setType(state.Type); |
| setVersion(state.Version.toString()); |
| setProvider(state.Provider); |
| setDescription(state.Description); |
| setLocation(state.Location); |
| setProperties(state.Properties); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.equinox.p2.metadata.index.IIndexProvider#getIndex(java.lang.String) |
| */ |
| @SuppressWarnings("unchecked") |
| public IIndex<IInstallableUnit> getIndex(String memberName) { |
| IQueryable<IInstallableUnit> queryable = QueryUtil.compoundQueryable(loadedRepos); |
| if (queryable instanceof IIndexProvider<?>) { |
| return ((IIndexProvider<IInstallableUnit>) queryable).getIndex(memberName); |
| } |
| return null; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.equinox.p2.metadata.index.IIndexProvider#everything() |
| */ |
| @SuppressWarnings("unchecked") |
| public Iterator<IInstallableUnit> everything() { |
| IQueryable<IInstallableUnit> queryable = QueryUtil.compoundQueryable(loadedRepos); |
| if (queryable instanceof IIndexProvider<?>) { |
| return ((IIndexProvider<IInstallableUnit>) queryable).everything(); |
| } |
| return Collections.EMPTY_LIST.iterator(); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.equinox.p2.metadata.index.IIndexProvider#getManagedProperty(java.lang.Object, java.lang.String, java.lang.Object) |
| */ |
| @SuppressWarnings("unchecked") |
| public Object getManagedProperty(Object client, String memberName, Object key) { |
| IQueryable<IInstallableUnit> queryable = QueryUtil.compoundQueryable(loadedRepos); |
| if (queryable instanceof IIndexProvider<?>) { |
| return ((IIndexProvider<IInstallableUnit>) queryable).getManagedProperty(client, memberName, key); |
| } |
| return null; |
| } |
| |
| private void removeFromRepoManager(List<URI> currentLoadedRepositories) { |
| if (currentLoadedRepositories == null) |
| return; |
| for (URI loadedChild : currentLoadedRepositories) { |
| manager.removeRepository(loadedChild); |
| } |
| } |
| |
| private boolean shouldFailOnChildFailure(CompositeRepositoryState state) { |
| Map<String, String> repoProperties = state.getProperties(); |
| boolean failOnChildFailure = ATOMIC_LOADING_DEFAULT; |
| if (repoProperties != null) { |
| String value = repoProperties.get(PROP_ATOMIC_LOADING); |
| if (value != null) { |
| failOnChildFailure = Boolean.parseBoolean(value); |
| } |
| } |
| return failOnChildFailure; |
| } |
| |
| @Override |
| public String toString() { |
| return getChildren().toString(); |
| } |
| |
| } |