blob: c369fb8b04931b4b051bb6a43d78432c0a0d88c5 [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 implementation and ideas
* Code 9 - ongoing development
*******************************************************************************/
package org.eclipse.equinox.internal.provisional.p2.directorywatcher;
import java.io.File;
import java.net.URI;
import java.util.*;
import org.eclipse.core.runtime.*;
import org.eclipse.equinox.internal.p2.artifact.repository.simple.SimpleArtifactDescriptor;
import org.eclipse.equinox.internal.p2.core.helpers.*;
import org.eclipse.equinox.internal.p2.update.Site;
import org.eclipse.equinox.p2.core.IProvisioningAgent;
import org.eclipse.equinox.p2.core.ProvisionException;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.publisher.*;
import org.eclipse.equinox.p2.publisher.eclipse.BundlesAction;
import org.eclipse.equinox.p2.publisher.eclipse.FeaturesAction;
import org.eclipse.equinox.p2.query.*;
import org.eclipse.equinox.p2.repository.artifact.*;
import org.eclipse.equinox.p2.repository.metadata.IMetadataRepository;
import org.eclipse.equinox.p2.repository.metadata.IMetadataRepositoryManager;
import org.eclipse.osgi.service.resolver.BundleDescription;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.*;
public class RepositoryListener extends DirectoryChangeListener {
public static final String ARTIFACT_FOLDER = "artifact.folder"; //$NON-NLS-1$
public static final String ARTIFACT_REFERENCE = "artifact.reference"; //$NON-NLS-1$
public static final String FILE_LAST_MODIFIED = "file.lastModified"; //$NON-NLS-1$
public static final String FILE_NAME = "file.name"; //$NON-NLS-1$
private final IMetadataRepository metadataRepository;
private final CachingArtifactRepository artifactRepository;
// at any point in time currentFiles is the list of files/dirs that the watcher has seen and
// believes to be on disk.
private final Map<File, Long> currentFiles = new HashMap<>();
private final Collection<File> polledSeenFiles = new HashSet<>();
private EntryAdvice advice = new EntryAdvice();
private PublisherInfo info;
private IPublisherResult iusToAdd;
private IPublisherResult iusToChange;
/**
* Create a repository listener that watches the specified folder and generates repositories
* for its content.
* @param repositoryName the repository name to use for the repository
* @param properties the map of repository properties or <code>null</code>
*/
public RepositoryListener(String repositoryName, Map<String, String> properties) {
URI location = getDefaultRepositoryLocation(this, repositoryName);
metadataRepository = initializeMetadataRepository(repositoryName, location, properties);
artifactRepository = initializeArtifactRepository(repositoryName, location, properties);
initializePublisher();
}
public RepositoryListener(IMetadataRepository metadataRepository, IArtifactRepository artifactRepository) {
this.artifactRepository = new CachingArtifactRepository(artifactRepository);
this.metadataRepository = metadataRepository;
initializePublisher();
}
private void initializePublisher() {
info = new PublisherInfo();
info.setArtifactRepository(artifactRepository);
info.setMetadataRepository(metadataRepository);
info.addAdvice(advice);
info.setArtifactOptions(IPublisherInfo.A_INDEX | IPublisherInfo.A_NO_MD5);
}
protected CachingArtifactRepository initializeArtifactRepository(String name, URI repositoryLocation, Map<String, String> properties) {
IArtifactRepositoryManager manager = getArtifactRepositoryManager();
if (manager == null)
throw new IllegalStateException(Messages.artifact_repo_manager_not_registered);
try {
IArtifactRepository result = manager.loadRepository(repositoryLocation, null);
return result == null ? null : new CachingArtifactRepository(result);
} catch (ProvisionException e) {
//fall through and create a new repository
}
try {
IArtifactRepository result = manager.createRepository(repositoryLocation, name, IArtifactRepositoryManager.TYPE_SIMPLE_REPOSITORY, properties);
return result == null ? null : new CachingArtifactRepository(result);
} catch (ProvisionException e) {
LogHelper.log(e);
throw new IllegalStateException(NLS.bind(Messages.failed_create_artifact_repo, repositoryLocation));
}
}
protected IMetadataRepository initializeMetadataRepository(String name, URI repositoryLocation, Map<String, String> properties) {
IMetadataRepositoryManager manager = getMetadataRepositoryManager();
if (manager == null)
throw new IllegalStateException(Messages.metadata_repo_manager_not_registered);
try {
return manager.loadRepository(repositoryLocation, null);
} catch (ProvisionException e) {
//fall through and create new repository
}
try {
return manager.createRepository(repositoryLocation, name, IMetadataRepositoryManager.TYPE_SIMPLE_REPOSITORY, properties);
} catch (ProvisionException e) {
LogHelper.log(e);
throw new IllegalStateException(NLS.bind(Messages.failed_create_metadata_repo, repositoryLocation));
}
}
@Override
public boolean added(File file) {
return process(file, true);
}
@Override
public boolean changed(File file) {
return process(file, false);
}
@Override
public boolean removed(File file) {
// the IUs and artifacts associated with this file will get removed in stopPoll
return currentFiles.containsKey(file);
}
private boolean process(File file, boolean isAddition) {
boolean isDirectory = file.isDirectory();
// is it a feature ?
if (isDirectory && file.getParentFile() != null && file.getParentFile().getName().equals("features") && new File(file, "feature.xml").exists()) //$NON-NLS-1$ //$NON-NLS-2$)
return processFeature(file, isAddition);
// could it be a bundle ?
if (isDirectory || file.getName().endsWith(".jar")) //$NON-NLS-1$
return processBundle(file, isDirectory, isAddition);
return false;
}
private boolean processBundle(File file, boolean isDirectory, boolean isAddition) {
BundleDescription bundleDescription = BundlesAction.createBundleDescriptionIgnoringExceptions(file);
if (bundleDescription == null)
return false;
advice.setProperties(file, file.lastModified(), file.toURI());
return publish(new BundlesAction(new BundleDescription[] {bundleDescription}), isAddition);
// TODO see bug 222370
// we only want to return the bundle IU so must exclude all fragment IUs
// not sure if this is still relevant but we should investigate.
}
private boolean processFeature(File file, boolean isAddition) {
String link = metadataRepository.getProperties().get(Site.PROP_LINK_FILE);
advice.setProperties(file, file.lastModified(), file.toURI(), link);
return publish(new FeaturesAction(new File[] {file}), isAddition);
}
private boolean publish(IPublisherAction action, boolean isAddition) {
IPublisherResult result = isAddition ? iusToAdd : iusToChange;
return action.perform(info, result, new NullProgressMonitor()).isOK();
}
@Override
public boolean isInterested(File file) {
return true;
}
@Override
public Long getSeenFile(File file) {
Long lastSeen = currentFiles.get(file);
if (lastSeen != null)
polledSeenFiles.add(file);
return lastSeen;
}
@Override
public void startPoll() {
iusToAdd = new PublisherResult();
iusToChange = new PublisherResult();
synchronizeCurrentFiles();
}
@Override
public void stopPoll() {
final Set<File> filesToRemove = new HashSet<>(currentFiles.keySet());
filesToRemove.removeAll(polledSeenFiles);
polledSeenFiles.clear();
synchronizeMetadataRepository(filesToRemove);
synchronizeArtifactRepository(filesToRemove);
iusToAdd = null;
iusToChange = null;
}
/**
* Flush all the pending changes to the metadata repository.
*/
private void synchronizeMetadataRepository(final Collection<File> removedFiles) {
if (metadataRepository == null)
return;
final Collection<IInstallableUnit> changes = iusToChange.getIUs(null, null);
// first remove any IUs that have changed or that are associated with removed files
if (!removedFiles.isEmpty() || !changes.isEmpty()) {
metadataRepository.removeInstallableUnits(changes);
// create a query that will identify all ius related to removed files.
// We convert the java.io.File objects to Strings before doing the comparison
// because when we have large numbers of files, the performance is much better.
// See bug 324353.
Collection<String> paths = new HashSet<>(removedFiles.size());
for (File file : removedFiles)
paths.add(file.getAbsolutePath());
IQuery<IInstallableUnit> removeQuery = QueryUtil.createMatchQuery( //
"$1.exists(x | properties[$0] == x)", FILE_NAME, paths); //$NON-NLS-1$
IQueryResult<IInstallableUnit> toRemove = metadataRepository.query(removeQuery, null);
metadataRepository.removeInstallableUnits(toRemove.toUnmodifiableSet());
}
// Then add all the new IUs as well as the new copies of the ones that have changed
Collection<IInstallableUnit> additions = iusToAdd.getIUs(null, null);
additions.addAll(changes);
if (!additions.isEmpty())
metadataRepository.addInstallableUnits(additions);
}
/**
* Here the artifacts have all been added to the artifact repo. Remove the
* descriptors related to any file that has been removed and flush the repo
* to ensure that all the additions and removals have been completed.
*/
private void synchronizeArtifactRepository(final Collection<File> removedFiles) {
if (artifactRepository == null)
return;
if (!removedFiles.isEmpty()) {
IArtifactDescriptor[] descriptors = artifactRepository.descriptorQueryable().query(ArtifactDescriptorQuery.ALL_DESCRIPTORS, null).toArray(IArtifactDescriptor.class);
for (IArtifactDescriptor d : descriptors) {
SimpleArtifactDescriptor descriptor = (SimpleArtifactDescriptor) d;
String filename = descriptor.getRepositoryProperty(FILE_NAME);
if (filename == null) {
if (Tracing.DEBUG) {
String message = NLS.bind(Messages.filename_missing, "artifact", descriptor.getArtifactKey()); //$NON-NLS-1$
LogHelper.log(new Status(IStatus.ERROR, Constants.BUNDLE_ID, message, null));
}
} else {
File artifactFile = new File(filename);
if (removedFiles.contains(artifactFile))
artifactRepository.removeDescriptor(descriptor);
}
}
}
artifactRepository.save();
}
/**
* Prime the list of current files that the listener knows about. This traverses the
* repos and looks for the related filename and modified timestamp information.
*/
private void synchronizeCurrentFiles() {
currentFiles.clear();
if (metadataRepository != null) {
IQueryResult<IInstallableUnit> ius = metadataRepository.query(QueryUtil.createIUAnyQuery(), null);
for (IInstallableUnit iu : ius) {
String filename = iu.getProperty(FILE_NAME);
if (filename == null) {
if (Tracing.DEBUG) {
String message = NLS.bind(Messages.filename_missing, "installable unit", iu.getId()); //$NON-NLS-1$
LogHelper.log(new Status(IStatus.ERROR, Constants.BUNDLE_ID, message, null));
}
} else {
File iuFile = new File(filename);
Long iuLastModified = Long.valueOf(iu.getProperty(FILE_LAST_MODIFIED));
currentFiles.put(iuFile, iuLastModified);
}
}
}
//
// // TODO should we be doing this for the artifact repo? the metadata repo should
// // be the main driver here.
// if (artifactRepository != null) {
// final List keys = new ArrayList(Arrays.asList(artifactRepository.getArtifactKeys()));
// for (Iterator it = keys.iterator(); it.hasNext();) {
// IArtifactKey key = (IArtifactKey) it.next();
// IArtifactDescriptor[] descriptors = artifactRepository.getArtifactDescriptors(key);
// for (int i = 0; i < descriptors.length; i++) {
// ArtifactDescriptor descriptor = (ArtifactDescriptor) descriptors[i];
// File artifactFile = new File(descriptor.getRepositoryProperty(FILE_NAME));
// Long artifactLastModified = new Long(descriptor.getRepositoryProperty(FILE_LAST_MODIFIED));
// currentFiles.put(artifactFile, artifactLastModified);
// }
// }
// }
}
public IMetadataRepository getMetadataRepository() {
return metadataRepository;
}
public IArtifactRepository getArtifactRepository() {
return artifactRepository;
}
private static IMetadataRepositoryManager getMetadataRepositoryManager() {
BundleContext bundleContext = FrameworkUtil.getBundle(RepositoryListener.class).getBundleContext();
return ServiceHelper.getService(bundleContext, IProvisioningAgent.class).getService(IMetadataRepositoryManager.class);
}
private static IArtifactRepositoryManager getArtifactRepositoryManager() {
BundleContext bundleContext = FrameworkUtil.getBundle(RepositoryListener.class).getBundleContext();
return ServiceHelper.getService(bundleContext, IProvisioningAgent.class).getService(IArtifactRepositoryManager.class);
}
private static URI getDefaultRepositoryLocation(Object object, String repositoryName) {
Bundle bundle = FrameworkUtil.getBundle(object.getClass());
BundleContext context = bundle.getBundleContext();
File base = context.getDataFile(""); //$NON-NLS-1$
File result = new File(base, "listener_" + repositoryName.hashCode()); //$NON-NLS-1$
result.mkdirs();
return result.toURI();
}
}