blob: 1eab34941513ca912206827f74d780af0f571a9f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008 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
* Code 9 - ongoing development
*******************************************************************************/
package org.eclipse.equinox.internal.p2.extensionlocation;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.equinox.internal.p2.core.helpers.LogHelper;
import org.eclipse.equinox.internal.p2.core.helpers.URLUtil;
import org.eclipse.equinox.internal.p2.publisher.eclipse.FeatureParser;
import org.eclipse.equinox.internal.p2.update.Site;
import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
import org.eclipse.equinox.internal.provisional.p2.directorywatcher.*;
import org.eclipse.equinox.p2.publisher.eclipse.*;
import org.eclipse.osgi.service.resolver.BundleDescription;
/**
* @since 1.0
*/
public class SiteListener extends DirectoryChangeListener {
public static final String SITE_POLICY = "org.eclipse.update.site.policy"; //$NON-NLS-1$
public static final String SITE_LIST = "org.eclipse.update.site.list"; //$NON-NLS-1$
private static final String FEATURES = "features"; //$NON-NLS-1$
private static final String PLUGINS = "plugins"; //$NON-NLS-1$
private static final String FEATURE_MANIFEST = "feature.xml"; //$NON-NLS-1$
public static final Object UNINITIALIZED = "uninitialized"; //$NON-NLS-1$
public static final Object INITIALIZING = "initializing"; //$NON-NLS-1$
public static final Object INITIALIZED = "initialized"; //$NON-NLS-1$
private String policy;
private String[] list;
private String url;
private DirectoryChangeListener delegate;
private String[] managedFiles;
private String[] toBeRemoved;
/*
* Return true if the given list contains the full path of the given file
* handle. Return false otherwise.
*/
private static boolean contains(String[] plugins, File file) {
String filename = file.getAbsolutePath();
for (int i = 0; i < plugins.length; i++)
if (filename.endsWith(new File(plugins[i]).toString()))
return true;
return false;
}
/**
* Given one repo and a base location, ensure cause the other repo to be loaded and then
* poll the base location once updating the repositories accordingly. This method is used to
* ensure that both the metadata and artifact repos corresponding to one location are
* synchronized in one go. It is expected that both repos have been previously created
* so simply loading them here will work and that all their properties etc have been configured
* previously.
* @param metadataRepository
* @param artifactRepository
* @param base
*/
public static synchronized void synchronizeRepositories(ExtensionLocationMetadataRepository metadataRepository, ExtensionLocationArtifactRepository artifactRepository, File base) {
try {
if (metadataRepository == null) {
artifactRepository.reload();
ExtensionLocationMetadataRepositoryFactory factory = new ExtensionLocationMetadataRepositoryFactory();
metadataRepository = (ExtensionLocationMetadataRepository) factory.load(artifactRepository.getLocation(), null);
} else if (artifactRepository == null) {
metadataRepository.reload();
ExtensionLocationArtifactRepositoryFactory factory = new ExtensionLocationArtifactRepositoryFactory();
artifactRepository = (ExtensionLocationArtifactRepository) factory.load(metadataRepository.getLocation(), null);
}
} catch (ProvisionException e) {
// TODO need proper error handling here. What should we do if there is a failure
// when loading "the other" repo?
e.printStackTrace();
return;
}
artifactRepository.state(INITIALIZING);
metadataRepository.state(INITIALIZING);
File plugins = new File(base, PLUGINS);
File features = new File(base, FEATURES);
DirectoryWatcher watcher = new DirectoryWatcher(new File[] {plugins, features});
// here we have to sync with the inner repos as the extension location repos are
// read-only wrappers.
DirectoryChangeListener listener = new RepositoryListener(metadataRepository.metadataRepository, artifactRepository.artifactRepository);
if (metadataRepository.getProperties().get(SiteListener.SITE_POLICY) != null)
listener = new SiteListener(metadataRepository.getProperties(), metadataRepository.getLocation().toExternalForm(), new BundlePoolFilteredListener(listener));
watcher.addListener(listener);
watcher.poll();
artifactRepository.state(INITIALIZED);
metadataRepository.state(INITIALIZED);
}
/*
* Create a new site listener on the given site.
*/
public SiteListener(Map properties, String url, DirectoryChangeListener delegate) {
this.url = url;
this.delegate = delegate;
this.policy = (String) properties.get(SITE_POLICY);
Collection listCollection = new HashSet();
String listString = (String) properties.get(SITE_LIST);
if (listString != null)
for (StringTokenizer tokenizer = new StringTokenizer(listString, ","); tokenizer.hasMoreTokens();) //$NON-NLS-1$
listCollection.add(tokenizer.nextToken());
this.list = (String[]) listCollection.toArray(new String[listCollection.size()]);
}
/* (non-Javadoc)
* @see org.eclipse.equinox.internal.provisional.p2.directorywatcher.DirectoryChangeListener#isInterested(java.io.File)
*/
public boolean isInterested(File file) {
// make sure that our delegate and super-class are both interested in
// the file before we consider it
if (!delegate.isInterested(file))
return false;
if (Site.POLICY_MANAGED_ONLY.equals(policy)) {
// we only want plug-ins referenced by features
return contains(getManagedFiles(), file);
} else if (Site.POLICY_USER_EXCLUDE.equals(policy)) {
// ensure the file doesn't refer to a plug-in in our list
if (contains(list, file))
return false;
} else if (Site.POLICY_USER_INCLUDE.equals(policy)) {
if (isFeature(file))
return true;
// we are only interested in plug-ins in the list
if (!contains(list, file))
return false;
} else {
// shouldn't happen... unknown policy type
return false;
}
// at this point we have either a user-include or user-exclude policy set
// and we think we are interested in the file. we should first check to
// see if it is in the list of things to be removed
return !isToBeRemoved(file);
}
private boolean isFeature(File file) {
String parent = file.getParent();
return parent != null && parent.endsWith(FEATURES);
}
/*
* Return a boolean value indicating whether or not the feature pointed to
* by the given file is in the update manager's list of features to be
* uninstalled in its clean-up phase.
*/
private boolean isToBeRemoved(File file) {
String[] removed = getToBeRemoved();
if (removed.length == 0)
return false;
Feature feature = getFeature(file);
if (feature == null)
return false;
for (int i = 0; i < removed.length; i++) {
String line = removed[i];
// the line is a versioned identifier which is id_version
if (line.equals(feature.getId() + '_' + feature.getVersion()))
return true;
}
return false;
}
/*
* Parse and return the feature.xml file in the given location.
* Can return null.
*/
private Feature getFeature(File location) {
if (location.isFile())
return null;
File manifest = new File(location, FEATURE_MANIFEST);
if (!manifest.exists())
return null;
FeatureParser parser = new FeatureParser();
return parser.parse(location);
}
/*
* Return an array describing the list of features are are going
* to be removed by the update manager in its clean-up phase.
* The strings are in the format of versioned identifiers: id_version
*/
private String[] getToBeRemoved() {
if (toBeRemoved != null)
return toBeRemoved;
File configurationLocation = Activator.getConfigurationLocation();
if (configurationLocation == null) {
LogHelper.log(new Status(IStatus.ERROR, Activator.ID, "Unable to compute the configuration location.")); //$NON-NLS-1$
toBeRemoved = new String[0];
return toBeRemoved;
}
File toBeUninstalledFile = new File(configurationLocation, "org.eclipse.update/toBeUninstalled"); //$NON-NLS-1$
if (!toBeUninstalledFile.exists()) {
toBeRemoved = new String[0];
return toBeRemoved;
}
// set it to be empty here in case we don't have a match in the file
toBeRemoved = new String[0];
Properties properties = new Properties();
InputStream input = null;
try {
input = new BufferedInputStream(new FileInputStream(toBeUninstalledFile));
properties.load(input);
} catch (IOException e) {
// TODO
} finally {
try {
if (input != null)
input.close();
} catch (IOException e) {
// ignore
}
}
String urlString = url;
if (urlString.endsWith(Constants.EXTENSION_LOCATION))
urlString = urlString.substring(0, urlString.length() - Constants.EXTENSION_LOCATION.length());
List result = new ArrayList();
for (Enumeration e = properties.elements(); e.hasMoreElements();) {
String line = (String) e.nextElement();
StringTokenizer tokenizer = new StringTokenizer(line, ";"); //$NON-NLS-1$
String targetSite = tokenizer.nextToken();
if (!urlString.equals(targetSite))
continue;
result.add(tokenizer.nextToken());
}
toBeRemoved = (String[]) result.toArray(new String[result.size()]);
return toBeRemoved;
}
/*
* Return an array of files which are managed. This includes all of the features
* for this site, as well as the locations for all the plug-ins referenced by those
* features.
*/
private String[] getManagedFiles() {
if (managedFiles != null)
return managedFiles;
List result = new ArrayList();
File siteLocation;
try {
siteLocation = URLUtil.toFile(new URL(url));
} catch (MalformedURLException e) {
LogHelper.log(new Status(IStatus.ERROR, Activator.ID, "Unable to create a URL from site location: " + url, e)); //$NON-NLS-1$
return new String[0];
}
Map pluginCache = getPlugins(siteLocation);
Map featureCache = getFeatures(siteLocation);
for (Iterator iter = featureCache.keySet().iterator(); iter.hasNext();) {
File featureFile = (File) iter.next();
// add the feature path
result.add(featureFile.toString());
Feature feature = (Feature) featureCache.get(featureFile);
FeatureEntry[] entries = feature.getEntries();
for (int inner = 0; inner < entries.length; inner++) {
FeatureEntry entry = entries[inner];
// grab the right location from the plug-in cache
String key = entry.getId() + '/' + entry.getVersion();
File pluginLocation = (File) pluginCache.get(key);
if (pluginLocation != null)
result.add(pluginLocation.toString());
}
}
managedFiles = (String[]) result.toArray(new String[result.size()]);
return managedFiles;
}
/*
* Iterate over the feature directory and return a map of
* File to Feature objects (from the generator bundle)
*/
private Map getFeatures(File siteLocation) {
Map result = new HashMap();
File featureDir = new File(siteLocation, FEATURES);
File[] children = featureDir.listFiles();
for (int i = 0; i < children.length; i++) {
File featureLocation = children[i];
if (featureLocation.isDirectory() && featureLocation.getParentFile() != null && featureLocation.getParentFile().getName().equals("features") && new File(featureLocation, "feature.xml").exists()) {//$NON-NLS-1$ //$NON-NLS-2$
FeatureParser parser = new FeatureParser();
Feature entry = parser.parse(featureLocation);
if (entry != null)
result.put(featureLocation, entry);
}
}
return result;
}
/*
* Iterate over the plugins directory and return a map of
* plug-in id/version to File locations.
*/
private Map getPlugins(File siteLocation) {
File[] plugins = new File(siteLocation, PLUGINS).listFiles();
Map result = new HashMap();
for (int i = 0; plugins != null && i < plugins.length; i++) {
File bundleLocation = plugins[i];
if (bundleLocation.isDirectory() || bundleLocation.getName().endsWith(".jar")) { //$NON-NLS-1$
BundleDescription description = BundlesAction.createBundleDescription(bundleLocation);
if (description != null) {
String id = description.getSymbolicName();
String version = description.getVersion().toString();
result.put(id + '/' + version, bundleLocation);
}
}
}
return result;
}
/* (non-Javadoc)
* @see org.eclipse.equinox.internal.provisional.p2.directorywatcher.RepositoryListener#added(java.io.File)
*/
public boolean added(File file) {
return delegate.added(file);
}
/* (non-Javadoc)
* @see org.eclipse.equinox.internal.provisional.p2.directorywatcher.RepositoryListener#changed(java.io.File)
*/
public boolean changed(File file) {
return delegate.changed(file);
}
/* (non-Javadoc)
* @see org.eclipse.equinox.internal.provisional.p2.directorywatcher.RepositoryListener#getSeenFile(java.io.File)
*/
public Long getSeenFile(File file) {
return delegate.getSeenFile(file);
}
/* (non-Javadoc)
* @see org.eclipse.equinox.internal.provisional.p2.directorywatcher.RepositoryListener#removed(java.io.File)
*/
public boolean removed(File file) {
return delegate.removed(file);
}
/* (non-Javadoc)
* @see org.eclipse.equinox.internal.provisional.p2.directorywatcher.RepositoryListener#startPoll()
*/
public void startPoll() {
delegate.startPoll();
}
/* (non-Javadoc)
* @see org.eclipse.equinox.internal.provisional.p2.directorywatcher.RepositoryListener#stopPoll()
*/
public void stopPoll() {
delegate.stopPoll();
}
}