blob: af080a0a806b8538e5696b2546b105cf0b8055c7 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2018 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Prashant Deva - Bug 194674 [prov] Provide write access to metadata repository
*******************************************************************************/
package org.eclipse.equinox.internal.p2.metadata.repository;
import java.io.*;
import java.net.URI;
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.metadata.*;
import org.eclipse.equinox.internal.p2.metadata.index.*;
import org.eclipse.equinox.internal.provisional.p2.core.eventbus.IProvisioningEventBus;
import org.eclipse.equinox.internal.provisional.p2.repository.RepositoryEvent;
import org.eclipse.equinox.p2.core.*;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.metadata.KeyWithLocale;
import org.eclipse.equinox.p2.metadata.index.IIndex;
import org.eclipse.equinox.p2.metadata.index.IIndexProvider;
import org.eclipse.equinox.p2.query.IQuery;
import org.eclipse.equinox.p2.query.IQueryResult;
import org.eclipse.equinox.p2.repository.*;
import org.eclipse.equinox.p2.repository.metadata.IMetadataRepositoryManager;
import org.eclipse.equinox.p2.repository.metadata.spi.AbstractMetadataRepository;
/**
* A metadata repository that resides in the local file system. If the repository
* location is a directory, this implementation will traverse the directory structure
* and combine any metadata repository files that are found.
*/
public class LocalMetadataRepository extends AbstractMetadataRepository implements IIndexProvider<IInstallableUnit> {
static final private String CONTENT_FILENAME = "content"; //$NON-NLS-1$
static final private String REPOSITORY_TYPE = LocalMetadataRepository.class.getName();
static final private Integer REPOSITORY_VERSION = 1;
static final private String JAR_EXTENSION = ".jar"; //$NON-NLS-1$
static final private String XML_EXTENSION = ".xml"; //$NON-NLS-1$
protected IUMap units = new IUMap();
protected HashSet<IRepositoryReference> repositories = new HashSet<>();
private IIndex<IInstallableUnit> idIndex;
private IIndex<IInstallableUnit> capabilityIndex;
private TranslationSupport translationSupport;
private boolean snapshotNeeded = false;
private boolean disableSave = false;
private static File getActualLocation(URI location, String extension) {
File spec = URIUtil.toFile(location);
String path = spec.getAbsolutePath();
if (path.endsWith(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 += CONTENT_FILENAME;
else
path += "/" + CONTENT_FILENAME; //$NON-NLS-1$
return new File(path + extension);
}
public static File getActualLocation(URI location) {
return getActualLocation(location, XML_EXTENSION);
}
/**
* This no argument constructor is called when restoring an existing repository.
*/
public LocalMetadataRepository(IProvisioningAgent agent) {
super(agent);
}
/**
* This constructor is used when creating a new local repository.
* @param location The location of the repository
* @param name The name of the repository
*/
public LocalMetadataRepository(IProvisioningAgent agent, URI location, String name, Map<String, String> properties) {
super(agent, name == null ? (location != null ? location.toString() : "") : name, REPOSITORY_TYPE, REPOSITORY_VERSION.toString(), location, null, null, properties); //$NON-NLS-1$
if (!location.getScheme().equals("file")) //$NON-NLS-1$
throw new IllegalArgumentException("Invalid local repository location: " + location); //$NON-NLS-1$
//when creating a repository, we must ensure it exists on disk so a subsequent load will succeed
save();
}
/* (non-Javadoc)
* @see org.eclipse.equinox.p2.repository.metadata.spi.AbstractMetadataRepository#addInstallableUnits(java.util.Collection)
*/
@Override
public synchronized void addInstallableUnits(Collection<IInstallableUnit> installableUnits) {
if (installableUnits == null || installableUnits.isEmpty())
return;
if (snapshotNeeded) {
units = units.clone();
idIndex = null; // Backed by units
snapshotNeeded = false;
}
units.addAll(installableUnits);
capabilityIndex = null; // Generated, not backed by units
save();
}
@Override
public void addReferences(Collection<? extends IRepositoryReference> references) {
assertModifiable();
// only write out the repository if we made changes
if (repositories.addAll(references))
save();
}
@Override
public Collection<IRepositoryReference> getReferences() {
return Collections.unmodifiableCollection(repositories);
}
@Override
public synchronized IIndex<IInstallableUnit> getIndex(String memberName) {
if (InstallableUnit.MEMBER_ID.equals(memberName)) {
snapshotNeeded = true;
if (idIndex == null)
idIndex = new IdIndex(units);
return idIndex;
}
if (InstallableUnit.MEMBER_PROVIDED_CAPABILITIES.equals(memberName)) {
snapshotNeeded = true;
if (capabilityIndex == null)
capabilityIndex = new CapabilityIndex(units.iterator());
return capabilityIndex;
}
return null;
}
@Override
public synchronized Object getManagedProperty(Object client, String memberName, Object key) {
if (!(client instanceof IInstallableUnit))
return null;
IInstallableUnit iu = (IInstallableUnit) client;
if (InstallableUnit.MEMBER_TRANSLATED_PROPERTIES.equals(memberName)) {
if (translationSupport == null)
translationSupport = new TranslationSupport(this);
return key instanceof KeyWithLocale ? translationSupport.getIUProperty(iu, (KeyWithLocale) key) : translationSupport.getIUProperty(iu, key.toString());
}
return null;
}
@Override
public void initialize(RepositoryState state) {
synchronized (this) {
setName(state.Name);
setType(state.Type);
setVersion(state.Version.toString());
setProvider(state.Provider);
setDescription(state.Description);
setLocation(state.Location);
setProperties(state.Properties);
this.units.addAll(state.Units);
this.repositories.addAll(Arrays.asList(state.Repositories));
}
publishRepositoryReferences();
}
/**
* Broadcast discovery events for all repositories referenced by this repository.
*/
public void publishRepositoryReferences() {
IProvisioningEventBus bus = getProvisioningAgent().getService(IProvisioningEventBus.class);
if (bus == null)
return;
List<IRepositoryReference> repositoriesSnapshot = createRepositoriesSnapshot();
for (IRepositoryReference reference : repositoriesSnapshot) {
boolean isEnabled = (reference.getOptions() & IRepository.ENABLED) != 0;
bus.publishEvent(new RepositoryEvent(reference.getLocation(), reference.getType(), RepositoryEvent.DISCOVERED, isEnabled));
}
}
private synchronized List<IRepositoryReference> createRepositoriesSnapshot() {
if (repositories.isEmpty())
return Collections.emptyList();
return new ArrayList<>(repositories);
}
// use this method to setup any transient fields etc after the object has been restored from a stream
public synchronized void initializeAfterLoad(URI aLocation) {
setLocation(aLocation);
}
@Override
public boolean isModifiable() {
return true;
}
@Override
public IQueryResult<IInstallableUnit> query(IQuery<IInstallableUnit> query, IProgressMonitor monitor) {
return IndexProvider.query(this, query, monitor);
}
@Override
public synchronized Iterator<IInstallableUnit> everything() {
snapshotNeeded = true;
return units.iterator();
}
@Override
public synchronized void removeAll() {
if (snapshotNeeded) {
units = new IUMap();
idIndex = null; // Backed by units
snapshotNeeded = false;
} else
units.clear();
capabilityIndex = null; // Generated, not backed by units.
save();
}
@Override
public synchronized boolean removeInstallableUnits(Collection<IInstallableUnit> installableUnits) {
boolean changed = false;
if (installableUnits != null && !installableUnits.isEmpty()) {
changed = true;
if (snapshotNeeded) {
units = units.clone();
idIndex = null; // Backed by units
snapshotNeeded = false;
}
units.removeAll(installableUnits);
capabilityIndex = null; // Generated, not backed by units.
}
if (changed)
save();
return changed;
}
// caller should be synchronized
/**
* Marking protected so we can test. This is internal, so it shouldn't matter, but I'll
* mark it as no override just to be clear.
* @nooverride This method is not intended to be re-implemented or extended by clients.
*/
protected void save() {
if (disableSave)
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 NullProgressMonitor());
new MetadataRepositoryIO(getProvisioningAgent()).write(this, output);
} catch (IOException e) {
LogHelper.log(new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_FAILED_WRITE, "Error saving metadata repository: " + getLocation(), e)); //$NON-NLS-1$
}
}
@Override
public String setProperty(String key, String newValue, IProgressMonitor monitor) {
try {
String oldValue = null;
synchronized (this) {
oldValue = super.setProperty(key, newValue, monitor);
if (oldValue == newValue || (oldValue != null && oldValue.equals(newValue)))
return oldValue;
save();
}
//force repository manager to reload this repository because it caches properties
MetadataRepositoryManager manager = (MetadataRepositoryManager) getProvisioningAgent()
.getService(IMetadataRepositoryManager.class);
if (manager.removeRepository(getLocation()))
manager.addRepository(this);
return oldValue;
} finally {
if (monitor != null)
monitor.done();
}
}
@Override
public IStatus executeBatch(IRunnableWithProgress runnable, IProgressMonitor monitor) {
IStatus result = null;
synchronized (this) {
try {
disableSave = true;
runnable.run(monitor);
} catch (OperationCanceledException oce) {
return new Status(IStatus.CANCEL, Activator.ID, oce.getMessage(), oce);
} catch (Throwable e) {
result = new Status(IStatus.ERROR, Activator.ID, e.getMessage(), e);
} finally {
disableSave = false;
try {
save();
} catch (Exception e) {
if (result != null)
result = new MultiStatus(Activator.ID, IStatus.ERROR, new IStatus[] {result}, e.getMessage(), e);
else
result = new Status(IStatus.ERROR, Activator.ID, e.getMessage(), e);
}
}
}
if (result == null)
result = Status.OK_STATUS;
return result;
}
@Override
public void compress(IPool<IInstallableUnit> iuPool) {
units.compress(iuPool);
}
}