blob: 362aa2eb3380e76a8d111183b50828160f6c80ec [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
*******************************************************************************/
package org.eclipse.equinox.internal.p2.publisher.features;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.*;
import org.eclipse.core.runtime.*;
import org.eclipse.equinox.internal.p2.core.helpers.FileUtils;
import org.eclipse.equinox.internal.p2.core.helpers.LogHelper;
import org.eclipse.equinox.internal.p2.publisher.Activator;
import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
import org.eclipse.osgi.util.NLS;
import org.xml.sax.SAXException;
/**
* @since 1.0
*/
public class UpdateSite {
private static final String VERSION_SEPARATOR = "_"; //$NON-NLS-1$
private static final String JAR_EXTENSION = ".jar"; //$NON-NLS-1$
private static final String FEATURE_DIR = "features/"; //$NON-NLS-1$
private static final String PLUGIN_DIR = "plugins/"; //$NON-NLS-1$
private static final String FEATURE_TEMP_FILE = "feature"; //$NON-NLS-1$
private static final String SITE_FILE = "site.xml"; //$NON-NLS-1$
private static final String DIR_SEPARATOR = "/"; //$NON-NLS-1$
private String checksum;
private URL location;
private SiteModel site;
/*
* Some variables for caching.
*/
// map of String (URL.toExternalForm()) to UpdateSite
private static Map siteCache = new HashMap();
// map of String (featureID_featureVersion) to Feature
private Map featureCache = new HashMap();
/*
* Return a new URL for the given file which is based from the specified root.
*/
public static URL getFileURL(URL root, String fileName) throws MalformedURLException {
if (root.getPath().endsWith(fileName))
return root;
if (root.getPath().endsWith(SITE_FILE))
return new URL(root, fileName);
if (root.getPath().endsWith(DIR_SEPARATOR))
return new URL(root.toExternalForm() + fileName);
return new URL(root.toExternalForm() + DIR_SEPARATOR + fileName);
}
/*
* Open and return the input stream for the given URL.
*/
private static InputStream getSiteInputStream(URL url) throws ProvisionException {
try {
return getSiteURL(url).openStream();
} catch (MalformedURLException e) {
String msg = NLS.bind(Messages.InvalidRepositoryLocation, url);
throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_INVALID_LOCATION, msg, e));
} catch (IllegalArgumentException e) {
//see bug 221600 - URL.openStream can throw IllegalArgumentException
String msg = NLS.bind(Messages.InvalidRepositoryLocation, url);
throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_INVALID_LOCATION, msg, e));
} catch (IOException e) {
String msg = NLS.bind(Messages.ErrorReadingSite, url);
throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_NOT_FOUND, msg, e));
}
}
/*
* Return a URL based on the given URL, which points to a site.xml file.
*/
private static URL getSiteURL(URL url) throws MalformedURLException {
if (url.getPath().endsWith(SITE_FILE))
return url;
if (url.getPath().endsWith(DIR_SEPARATOR))
return new URL(url.toExternalForm() + SITE_FILE);
return new URL(url.toExternalForm() + DIR_SEPARATOR + SITE_FILE);
}
/*
* Load and return an update site object from the given location.
*/
public static UpdateSite load(URL location, IProgressMonitor monitor) throws ProvisionException {
if (location == null)
return null;
UpdateSite result = (UpdateSite) siteCache.get(location.toExternalForm());
if (result != null)
return result;
InputStream input = getSiteInputStream(location);
try {
DefaultSiteParser siteParser = new DefaultSiteParser();
Checksum checksum = new CRC32();
input = new CheckedInputStream(new BufferedInputStream(input), checksum);
SiteModel siteModel = siteParser.parse(input);
String checksumString = Long.toString(checksum.getValue());
result = new UpdateSite(siteModel, location, checksumString);
siteCache.put(location.toExternalForm(), result);
return result;
} catch (SAXException e) {
String msg = NLS.bind(Messages.ErrorReadingSite, location);
throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_FAILED_READ, msg, e));
} catch (IOException e) {
String msg = NLS.bind(Messages.ErrorReadingSite, location);
throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_FAILED_READ, msg, e));
} finally {
try {
input.close();
} catch (IOException e) {
// ignore
}
}
}
/*
* Parse the feature.xml specified by the given input stream and return the feature object.
*/
private static Feature parseFeature(FeatureParser featureParser, URL featureURL) throws IOException, FileNotFoundException, ProvisionException {
File featureFile = File.createTempFile(FEATURE_TEMP_FILE, JAR_EXTENSION);
try {
FileUtils.copyStream(featureURL.openStream(), true, new BufferedOutputStream(new FileOutputStream(featureFile)), true);
return featureParser.parse(featureFile);
} catch (IllegalArgumentException e) {
//see bug 221600 - URL.openStream can throw IllegalArgumentException
String msg = NLS.bind(Messages.InvalidRepositoryLocation, featureURL.toExternalForm());
throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_INVALID_LOCATION, msg, e));
} finally {
featureFile.delete();
}
}
/*
* Throw an exception if the site pointed to by the given URL is not valid.
*/
public static void validate(URL url, IProgressMonitor monitor) throws ProvisionException {
InputStream input = getSiteInputStream(url);
try {
input.close();
} catch (IOException e) {
// ignore
}
}
/*
* Constructor for the class.
*/
private UpdateSite(SiteModel site, URL location, String checksum) {
super();
this.site = site;
this.location = location;
this.checksum = checksum;
}
/*
* Iterate over the archive entries in this site and return the matching URL string for
* the given identifier, if there is one.
*/
private URL getArchiveURL(URL base, String identifier) {
URLEntry[] archives = site.getArchives();
for (int i = 0; archives != null && i < archives.length; i++) {
URLEntry entry = archives[i];
if (identifier.equals(entry.getAnnotation()))
return internalGetURL(base, entry.getURL());
}
return null;
}
/*
* Return the checksum for this site.
*/
public String getChecksum() {
return checksum;
}
/*
* Return a URL which represents the location of the given feature.
*/
public URL getFeatureURL(SiteFeature siteFeature, String id, String version) {
URL base = site.getLocationURL();
if (base == null)
base = location;
if (siteFeature == null) {
SiteFeature[] entries = site.getFeatures();
for (int i = 0; i < entries.length; i++) {
if (id.equals(entries[i].getFeatureIdentifier()) && version.equals(entries[i].getFeatureVersion())) {
siteFeature = entries[i];
break;
}
}
}
if (siteFeature != null) {
URL url = siteFeature.getURL();
if (url != null)
return url;
url = getArchiveURL(base, id);
if (url != null)
return url;
}
// fall through to default URL
try {
return getFileURL(base, FEATURE_DIR + id + VERSION_SEPARATOR + version + JAR_EXTENSION);
} catch (MalformedURLException e) {
// shouldn't happen
}
return null;
}
/*
* Return the location of this site.
*/
public URL getLocation() {
return location;
}
/*
* Return a URL which represents the location of the given plug-in.
*/
public URL getPluginURL(FeatureEntry plugin) {
URL base = site.getLocationURL();
if (base == null)
base = location;
String path = PLUGIN_DIR + plugin.getId() + VERSION_SEPARATOR + plugin.getVersion() + JAR_EXTENSION;
URL url = getArchiveURL(base, path);
if (url != null)
return url;
try {
return getFileURL(base, path);
} catch (MalformedURLException e) {
// shouldn't happen
}
return null;
}
/*
* Return the site model.
*/
public SiteModel getSite() {
return site;
}
/*
* The trailing parameter can be either null, relative or absolute. If it is null,
* then return null. If it is absolute, then create a new url and return it. If it is
* relative, then make it relative to the given base url.
*/
private URL internalGetURL(URL base, String trailing) {
if (trailing == null)
return null;
try {
return new URL(trailing);
} catch (MalformedURLException e) {
try {
return new URL(base, trailing);
} catch (MalformedURLException e2) {
// shouldn't happen
}
}
return null;
}
/*
* Load and return the features references in this update site.
*/
public Feature[] loadFeatures() throws ProvisionException {
Feature[] result = loadFeaturesFromDigest();
return result == null ? loadFeaturesFromSite() : result;
}
/*
* Try and load the feature information from the update site's
* digest file, if it exists.
*/
private Feature[] loadFeaturesFromDigest() throws ProvisionException {
if (!featureCache.isEmpty())
return (Feature[]) featureCache.values().toArray(new Feature[featureCache.size()]);
try {
URL digestURL = getFileURL(location, "digest.zip"); //$NON-NLS-1$
File digestFile = File.createTempFile("digest", ".zip"); //$NON-NLS-1$ //$NON-NLS-2$
try {
FileUtils.copyStream(digestURL.openStream(), true, new BufferedOutputStream(new FileOutputStream(digestFile)), true);
Feature[] result = new DigestParser().parse(digestFile);
if (result == null)
return null;
for (int i = 0; i < result.length; i++) {
String key = result[i].getId() + VERSION_SEPARATOR + result[i].getVersion();
featureCache.put(key, result[i]);
}
return result;
} catch (IllegalArgumentException e) {
//see bug 221600 - URL.openStream can throw IllegalArgumentException
String msg = NLS.bind(Messages.InvalidRepositoryLocation, digestURL.toExternalForm());
throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_INVALID_LOCATION, msg, e));
} finally {
digestFile.delete();
}
} catch (MalformedURLException e) {
LogHelper.log(new Status(IStatus.ERROR, Activator.ID, NLS.bind(Messages.ErrorReadingDigest, location), e));
} catch (IOException e) {
LogHelper.log(new Status(IStatus.ERROR, Activator.ID, NLS.bind(Messages.ErrorReadingDigest, location), e));
}
return null;
}
/*
* Load and return the features that are referenced by this update site. Note this
* requires downloading and parsing the feature manifest locally.
*/
private Feature[] loadFeaturesFromSite() throws ProvisionException {
SiteFeature[] siteFeatures = site.getFeatures();
FeatureParser featureParser = new FeatureParser();
for (int i = 0; i < siteFeatures.length; i++) {
SiteFeature siteFeature = siteFeatures[i];
String key = siteFeature.getFeatureIdentifier() + VERSION_SEPARATOR + siteFeature.getFeatureVersion();
if (featureCache.containsKey(key))
continue;
URL featureURL = getFeatureURL(siteFeature, siteFeature.getFeatureIdentifier(), siteFeature.getFeatureVersion());
try {
Feature feature = parseFeature(featureParser, featureURL);
if (feature == null) {
LogHelper.log(new Status(IStatus.ERROR, Activator.ID, NLS.bind(Messages.ErrorReadingFeature, featureURL)));
} else {
featureCache.put(key, feature);
loadIncludedFeatures(feature, featureParser);
}
} catch (IOException e) {
LogHelper.log(new Status(IStatus.ERROR, Activator.ID, NLS.bind(Messages.ErrorReadingFeature, featureURL), e));
}
}
return (Feature[]) featureCache.values().toArray(new Feature[featureCache.size()]);
}
/*
* Load the features that are included by the given feature.
*/
private void loadIncludedFeatures(Feature feature, FeatureParser featureParser) throws ProvisionException {
FeatureEntry[] featureEntries = feature.getEntries();
for (int i = 0; i < featureEntries.length; i++) {
FeatureEntry entry = featureEntries[i];
if (entry.isRequires() || entry.isPlugin())
continue;
String key = entry.getId() + VERSION_SEPARATOR + entry.getVersion();
if (featureCache.containsKey(key))
continue;
URL featureURL = null;
try {
featureURL = getFileURL(location, FEATURE_DIR + entry.getId() + VERSION_SEPARATOR + entry.getVersion() + JAR_EXTENSION);
Feature includedFeature = parseFeature(featureParser, featureURL);
if (feature == null) {
LogHelper.log(new Status(IStatus.ERROR, Activator.ID, NLS.bind(Messages.ErrorReadingFeature, featureURL)));
} else {
featureCache.put(key, includedFeature);
loadIncludedFeatures(includedFeature, featureParser);
}
} catch (MalformedURLException e) {
LogHelper.log(new Status(IStatus.ERROR, Activator.ID, NLS.bind(Messages.ErrorReadingFeature, entry.getId()), e));
} catch (IOException e) {
LogHelper.log(new Status(IStatus.ERROR, Activator.ID, NLS.bind(Messages.ErrorReadingFeature, featureURL), e));
}
}
}
}