diff options
Diffstat (limited to 'bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2')
21 files changed, 1006 insertions, 350 deletions
diff --git a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/AbstractPublisherApplication.java b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/AbstractPublisherApplication.java index 65f75c199..1ad567f24 100644 --- a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/AbstractPublisherApplication.java +++ b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/AbstractPublisherApplication.java @@ -12,7 +12,6 @@ package org.eclipse.equinox.internal.p2.publisher; import java.io.File; import java.net.MalformedURLException; -import java.net.URL; import org.eclipse.core.runtime.IStatus; import org.eclipse.equinox.app.IApplication; import org.eclipse.equinox.app.IApplicationContext; @@ -20,12 +19,9 @@ import org.eclipse.equinox.internal.p2.artifact.repository.ArtifactRepositoryMan import org.eclipse.equinox.internal.p2.core.ProvisioningEventBus; import org.eclipse.equinox.internal.p2.core.helpers.ServiceHelper; import org.eclipse.equinox.internal.p2.metadata.repository.MetadataRepositoryManager; -import org.eclipse.equinox.internal.provisional.p2.artifact.repository.IArtifactRepository; import org.eclipse.equinox.internal.provisional.p2.artifact.repository.IArtifactRepositoryManager; import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException; import org.eclipse.equinox.internal.provisional.p2.core.eventbus.IProvisioningEventBus; -import org.eclipse.equinox.internal.provisional.p2.core.repository.IRepository; -import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepository; import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepositoryManager; import org.eclipse.osgi.util.NLS; import org.osgi.framework.ServiceRegistration; @@ -56,110 +52,32 @@ public abstract class AbstractPublisherApplication implements IApplication { protected String artifactLocation; protected String artifactRepoName; //whether repository xml files should be compressed - protected String compress = "false"; //$NON-NLS-1$ + protected boolean compress = false; protected boolean inplace = false; protected boolean append = false; protected boolean reusePackedFiles = false; protected String[] configurations; protected void initialize(PublisherInfo info) throws ProvisionException { - if (inplace) - initializeForInplace(info); - else - info.setArtifactOptions(info.getArtifactOptions() | IPublisherInfo.A_INDEX | IPublisherInfo.A_PUBLISH | IPublisherInfo.A_OVERWRITE); - initializeRepositories(info); - } - - protected void initializeArtifactRepository(PublisherInfo info) throws ProvisionException { - IArtifactRepositoryManager manager = (IArtifactRepositoryManager) ServiceHelper.getService(Activator.context, IArtifactRepositoryManager.class.getName()); - URL location; - try { - location = new URL(artifactLocation); - } catch (MalformedURLException e) { - throw new IllegalArgumentException(NLS.bind(Messages.exception_artifactRepoLocationURL, artifactLocation)); - } - try { - IArtifactRepository repository = manager.loadRepository(location, null); - if (!repository.isModifiable()) - throw new IllegalArgumentException(NLS.bind(Messages.exception_artifactRepoNotWritable, location)); - info.setArtifactRepository(repository); - if (reusePackedFiles) - repository.setProperty(PUBLISH_PACK_FILES_AS_SIBLINGS, "true"); //$NON-NLS-1$ - if (!append) - repository.removeAll(); - return; - } catch (ProvisionException e) { - //fall through and create a new repository - } - - // the given repo location is not an existing repo so we have to create something - // TODO for now create a Simple repo by default. - String repositoryName = artifactRepoName != null ? artifactRepoName : artifactLocation + " - artifacts"; //$NON-NLS-1$ - IArtifactRepository result = manager.createRepository(location, repositoryName, IArtifactRepositoryManager.TYPE_SIMPLE_REPOSITORY); - manager.addRepository(result.getLocation()); if (inplace) { - // TODO there must be something we have to do to set up the mapping rules here... - } - result.setProperty(IRepository.PROP_COMPRESSED, compress); - if (reusePackedFiles) - result.setProperty(PUBLISH_PACK_FILES_AS_SIBLINGS, "true"); //$NON-NLS-1$ - if (artifactRepoName != null) - result.setName(artifactRepoName); - info.setArtifactRepository(result); - } - - protected void initializeForInplace(PublisherInfo info) { - File location = new File(source); - try { - if (metadataLocation == null) - metadataLocation = location.toURL().toExternalForm(); - if (artifactLocation == null) - artifactLocation = location.toURL().toExternalForm(); - } catch (MalformedURLException e) { - // ought not happen... - } - info.setArtifactOptions(info.getArtifactOptions() | IPublisherInfo.A_INDEX | IPublisherInfo.A_PUBLISH); - } - - protected void initializeMetadataRepository(PublisherInfo info) throws ProvisionException { - URL location; - try { - location = new URL(metadataLocation); - } catch (MalformedURLException e) { - throw new IllegalArgumentException(NLS.bind(Messages.exception_metadataRepoLocationURL, artifactLocation)); - } - IMetadataRepositoryManager manager = (IMetadataRepositoryManager) ServiceHelper.getService(Activator.context, IMetadataRepositoryManager.class.getName()); - try { - IMetadataRepository repository = manager.loadRepository(location, null); - if (repository != null) { - repository.setProperty(IRepository.PROP_COMPRESSED, compress); - if (!repository.isModifiable()) - throw new IllegalArgumentException(NLS.bind(Messages.exception_metadataRepoNotWritable, location)); - info.setMetadataRepository(repository); - if (!append) - repository.removeAll(); - return; + File location = new File(source); + try { + if (metadataLocation == null) + metadataLocation = location.toURL().toExternalForm(); + if (artifactLocation == null) + artifactLocation = location.toURL().toExternalForm(); + } catch (MalformedURLException e) { + // ought not happen... } - } catch (ProvisionException e) { - //fall through and create a new repository - } - - // the given repo location is not an existing repo so we have to create something - // TODO for now create a random repo by default. - String repositoryName = metadataRepoName == null ? metadataLocation + " - metadata" : metadataRepoName; //$NON-NLS-1$ - IMetadataRepository result = manager.createRepository(location, repositoryName, IMetadataRepositoryManager.TYPE_SIMPLE_REPOSITORY); - manager.addRepository(result.getLocation()); - if (result != null) { - result.setProperty(IRepository.PROP_COMPRESSED, compress); - if (metadataRepoName != null) - result.setName(metadataRepoName); - info.setMetadataRepository(result); - } + info.setArtifactOptions(info.getArtifactOptions() | IPublisherInfo.A_INDEX | IPublisherInfo.A_PUBLISH); + } else + info.setArtifactOptions(info.getArtifactOptions() | IPublisherInfo.A_INDEX | IPublisherInfo.A_PUBLISH | IPublisherInfo.A_OVERWRITE); + initializeRepositories(info); } protected void initializeRepositories(PublisherInfo info) throws ProvisionException { - initializeArtifactRepository(info); - initializeMetadataRepository(info); + info.setArtifactRepository(Publisher.createArtifactRepository(artifactLocation, artifactRepoName, append, compress, reusePackedFiles)); + info.setMetadataRepository(Publisher.createMetadataRepository(metadataLocation, metadataRepoName, append, compress)); } protected void processCommandLineArguments(String[] args, PublisherInfo info) throws Exception { @@ -211,7 +129,7 @@ public abstract class AbstractPublisherApplication implements IApplication { append = true; if (arg.equalsIgnoreCase("-compress")) //$NON-NLS-1$ - compress = "true"; //$NON-NLS-1$ + compress = true; if (arg.equalsIgnoreCase("-reusePack200Files")) //$NON-NLS-1$ reusePackedFiles = true; diff --git a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/BundleDescriptionFactory.java b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/BundleDescriptionFactory.java index 776e78252..6190447b8 100644 --- a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/BundleDescriptionFactory.java +++ b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/BundleDescriptionFactory.java @@ -12,17 +12,18 @@ package org.eclipse.equinox.internal.p2.publisher; import java.io.*; import java.util.*; -import java.util.jar.*; +import java.util.jar.JarFile; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.eclipse.core.runtime.*; +import org.eclipse.equinox.internal.p2.core.helpers.LogHelper; import org.eclipse.equinox.internal.p2.core.helpers.ServiceHelper; -import org.eclipse.equinox.internal.p2.metadata.repository.Activator; import org.eclipse.osgi.service.pluginconversion.PluginConversionException; import org.eclipse.osgi.service.pluginconversion.PluginConverter; import org.eclipse.osgi.service.resolver.*; import org.eclipse.osgi.util.ManifestElement; import org.eclipse.osgi.util.NLS; +import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; public class BundleDescriptionFactory { @@ -44,6 +45,13 @@ public class BundleDescriptionFactory { StateObjectFactory factory; State state; + public static BundleDescriptionFactory getBundleDescriptionFactory(BundleContext context) { + PlatformAdmin platformAdmin = (PlatformAdmin) ServiceHelper.getService(context, PlatformAdmin.class.getName()); + if (platformAdmin == null) + throw new IllegalStateException("PlatformAdmin not registered."); //$NON-NLS-1$ + return new BundleDescriptionFactory(platformAdmin.getFactory(), null); + } + public BundleDescriptionFactory(StateObjectFactory factory, State state) { this.factory = factory; this.state = state; @@ -58,18 +66,20 @@ public class BundleDescriptionFactory { PluginConverter converter; try { converter = acquirePluginConverter(); - if (converter == null) + if (converter == null) { + LogHelper.log(new Status(IStatus.ERROR, Activator.ID, "Unable to aquire PluginConverter service during generation for: " + bundleLocation)); return null; + } return converter.convertManifest(bundleLocation, false, null, true, null); } catch (PluginConversionException convertException) { + // only log the exception if we had a plugin.xml or fragment.xml and we failed conversion if (bundleLocation.getName().equals(FEATURE_FILENAME_DESCRIPTOR)) return null; if (!new File(bundleLocation, PLUGIN_FILENAME_DESCRIPTOR).exists() && !new File(bundleLocation, FRAGMENT_FILENAME_DESCRIPTOR).exists()) return null; if (logConversionException) { IStatus status = new Status(IStatus.WARNING, Activator.ID, 0, NLS.bind(Messages.exception_errorConverting, bundleLocation.getAbsolutePath()), convertException); - System.out.println(status); - //TODO Need to find a way to get a logging service to log + LogHelper.log(status); } return null; } @@ -81,9 +91,9 @@ public class BundleDescriptionFactory { descriptor.setUserObject(enhancedManifest); return descriptor; } catch (BundleException e) { - // IStatus status = new Status(IStatus.WARNING, IPDEBuildConstants.PI_PDEBUILD, EXCEPTION_STATE_PROBLEM, NLS.bind(Messages.exception_stateAddition, enhancedManifest.get(Constants.BUNDLE_NAME)), e); - // BundleHelper.getDefault().getLog().log(status); - System.err.println(NLS.bind(Messages.exception_stateAddition, bundleLocation != null ? bundleLocation.getAbsoluteFile() : null)); + String message = NLS.bind(Messages.exception_stateAddition, bundleLocation == null ? null : bundleLocation.getAbsoluteFile()); + IStatus status = new Status(IStatus.WARNING, Activator.ID, message, e); + LogHelper.log(status); return null; } } @@ -101,11 +111,13 @@ public class BundleDescriptionFactory { ManifestElement.parseBundleManifest(manifestStream, entries); return getBundleDescription(entries, bundleLocation); } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + String message = "An error occurred while reading the bundle description."; + IStatus status = new Status(IStatus.ERROR, Activator.ID, message, e); + LogHelper.log(status); } catch (BundleException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + String message = "An error occurred while reading the bundle description."; + IStatus status = new Status(IStatus.ERROR, Activator.ID, message, e); + LogHelper.log(status); } return null; } @@ -121,25 +133,30 @@ public class BundleDescriptionFactory { manifestStream = jarFile.getInputStream(manifestEntry); } } else { - manifestStream = new BufferedInputStream(new FileInputStream(new File(bundleLocation, JarFile.MANIFEST_NAME))); + File manifestFile = new File(bundleLocation, JarFile.MANIFEST_NAME); + if (manifestFile.exists()) + manifestStream = new BufferedInputStream(new FileInputStream(manifestFile)); } } catch (IOException e) { - //ignore + //ignore but log + LogHelper.log(new Status(IStatus.WARNING, Activator.ID, "An error occurred while loading the bundle manifest.", e)); } Dictionary manifest = null; if (manifestStream != null) { try { - manifest = manifestToProperties(new Manifest(manifestStream).getMainAttributes()); - } catch (IOException ioe) { + Map manifestMap = ManifestElement.parseBundleManifest(manifestStream, null); + // TODO temporary hack. We are reading a Map but everyone wants a Dictionary so convert. + // real answer is to have people expect a Map but that is a wider change. + manifest = new Hashtable(manifestMap); + } catch (IOException e) { + LogHelper.log(new Status(IStatus.ERROR, Activator.ID, "An error occurred while loading the bundle manifest.", e)); + return null; + } catch (BundleException e) { + LogHelper.log(new Status(IStatus.ERROR, Activator.ID, "An error occurred while loading the bundle manifest.", e)); return null; } finally { try { - manifestStream.close(); - } catch (IOException e1) { - //Ignore - } - try { if (jarFile != null) jarFile.close(); } catch (IOException e2) { @@ -157,6 +174,8 @@ public class BundleDescriptionFactory { if (manifest.get(org.osgi.framework.Constants.BUNDLE_SYMBOLICNAME) == null) manifest = convertPluginManifest(bundleLocation, true); + if (manifest == null) + return null; // if the bundle itself does not define its shape, infer the shape from the current form if (manifest.get(BUNDLE_SHAPE) == null) manifest.put(BUNDLE_SHAPE, bundleLocation.isDirectory() ? DIR : JAR); @@ -344,12 +363,4 @@ public class BundleDescriptionFactory { // return localizedProperties; // } - private static Properties manifestToProperties(Attributes attributes) { - Properties result = new Properties(); - for (Iterator i = attributes.keySet().iterator(); i.hasNext();) { - Attributes.Name key = (Attributes.Name) i.next(); - result.put(key.toString(), attributes.get(key)); - } - return result; - } } diff --git a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/IPublisherInfo.java b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/IPublisherInfo.java index caba82ba6..17e29827a 100644 --- a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/IPublisherInfo.java +++ b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/IPublisherInfo.java @@ -34,6 +34,13 @@ public interface IPublisherInfo { public static final int A_OVERWRITE = 4; /** + * A bitwise flag to say whether or not to leave the disk content discovered + * as is when publishing an artifact. That is, if a directory is discovered, it is + * left as a directory. + */ + public static final int A_AS_IS = 8; + + /** * Returns the artifact repository into which any publishable artifacts are published * or <code>null</code> if none. * @return a destination artifact repository or <code>null</code> diff --git a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/InstallPublisherApplication.java b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/InstallPublisherApplication.java index bc47ceec0..f2f3b276e 100644 --- a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/InstallPublisherApplication.java +++ b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/InstallPublisherApplication.java @@ -20,7 +20,7 @@ public class InstallPublisherApplication extends AbstractPublisherApplication { protected String flavor; protected String[] topLevel; protected boolean start; - private String[] rootExclusions; + protected String[] rootExclusions; public InstallPublisherApplication() { } diff --git a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/MetadataGeneratorHelper.java b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/MetadataGeneratorHelper.java index 583f95eb5..fb3c73aba 100644 --- a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/MetadataGeneratorHelper.java +++ b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/MetadataGeneratorHelper.java @@ -39,6 +39,7 @@ public class MetadataGeneratorHelper { /** * A capability namespace representing the type of Eclipse resource (bundle, feature, source bundle, etc) * @see RequiredCapability#getNamespace() + * @see ProvidedCapability#getNamespace() */ public static final String NAMESPACE_ECLIPSE_TYPE = "org.eclipse.equinox.p2.eclipse.type"; //$NON-NLS-1$ @@ -46,6 +47,7 @@ public class MetadataGeneratorHelper { * A capability name in the {@link #NAMESPACE_ECLIPSE_TYPE} namespace * representing and OSGi bundle resource * @see RequiredCapability#getName() + * @see ProvidedCapability#getName() */ public static final String TYPE_ECLIPSE_BUNDLE = "bundle"; //$NON-NLS-1$ /** @@ -63,11 +65,13 @@ public class MetadataGeneratorHelper { public static final String TYPE_ECLIPSE_SOURCE = "source"; //$NON-NLS-1$ /** - * A capability name in the {@link #NAMESPACE_ECLIPSE_TYPE} namespace - * representing localized manifest properties - * @see RequiredCapability#getName() + * A capability namespace representing the localization (translation) + * of strings from a specified IU in a specified locale + * @see RequiredCapability#getNamespace() + * @see ProvidedCapability#getNamespace() + * TODO: this should be in API, probably in IInstallableUnit */ - public static final String TYPE_ECLIPSE_MANIFEST_LOCALIZATION = "manifest.localization"; //$NON-NLS-1$ + public static final String NAMESPACE_IU_LOCALIZATION = "org.eclipse.equinox.p2.localization"; //$NON-NLS-1$ // Only certain properties in the bundle manifest are assumed to be localized. public static final String[] BUNDLE_LOCALIZED_PROPERTIES = {Constants.BUNDLE_NAME, Constants.BUNDLE_DESCRIPTION, Constants.BUNDLE_VENDOR, Constants.BUNDLE_CONTACTADDRESS, Constants.BUNDLE_DOCURL, Constants.BUNDLE_UPDATELOCATION}; @@ -79,7 +83,7 @@ public class MetadataGeneratorHelper { private static final String CAPABILITY_NS_OSGI_BUNDLE = "osgi.bundle"; //$NON-NLS-1$ private static final String CAPABILITY_NS_OSGI_FRAGMENT = "osgi.fragment"; //$NON-NLS-1$ - private static final String CAPABILITY_NS_UPDATE_FEATURE = "org.eclipse.update.feature"; //$NON-NLS-1$ + public static final String CAPABILITY_NS_UPDATE_FEATURE = "org.eclipse.update.feature"; //$NON-NLS-1$ private static final Version DEFAULT_JRE_VERSION = new Version("1.6"); //$NON-NLS-1$ @@ -89,11 +93,11 @@ public class MetadataGeneratorHelper { public static final String INSTALL_FEATURES_FILTER = "(org.eclipse.update.install.features=true)"; //$NON-NLS-1$ - private static final String IU_NAMESPACE = IInstallableUnit.NAMESPACE_IU_ID; + public static final String IU_NAMESPACE = IInstallableUnit.NAMESPACE_IU_ID; private static final String LAUNCHER_ID_PREFIX = "org.eclipse.launcher"; //$NON-NLS-1$ - private static final String ECLIPSE_INSTALL_HANDLER_PROP = "org.eclipse.update.installHandler"; //$NON-NLS-1$ + public static final String ECLIPSE_INSTALL_HANDLER_PROP = "org.eclipse.update.installHandler"; //$NON-NLS-1$ //TODO - need to come up with a way to infer launcher version private static final Version LAUNCHER_VERSION = new Version(1, 0, 0); @@ -106,7 +110,6 @@ public class MetadataGeneratorHelper { public static final ProvidedCapability BUNDLE_CAPABILITY = MetadataFactory.createProvidedCapability(NAMESPACE_ECLIPSE_TYPE, TYPE_ECLIPSE_BUNDLE, new Version(1, 0, 0)); public static final ProvidedCapability FEATURE_CAPABILITY = MetadataFactory.createProvidedCapability(NAMESPACE_ECLIPSE_TYPE, TYPE_ECLIPSE_FEATURE, new Version(1, 0, 0)); public static final ProvidedCapability SOURCE_BUNDLE_CAPABILITY = MetadataFactory.createProvidedCapability(NAMESPACE_ECLIPSE_TYPE, TYPE_ECLIPSE_SOURCE, new Version(1, 0, 0)); - public static final ProvidedCapability MANIFEST_LOCALIZATION_CAPABILITY = MetadataFactory.createProvidedCapability(NAMESPACE_ECLIPSE_TYPE, TYPE_ECLIPSE_MANIFEST_LOCALIZATION, new Version(1, 0, 0)); static final String DEFAULT_BUNDLE_LOCALIZATION = "plugin"; //$NON-NLS-1$ static final String PROPERTIES_FILE_EXTENSION = ".properties"; //$NON-NLS-1$ @@ -115,7 +118,7 @@ public class MetadataGeneratorHelper { static final Locale DEFAULT_LOCALE = new Locale("df", "LT"); //$NON-NLS-1$//$NON-NLS-2$ static final Locale PSEUDO_LOCALE = new Locale("zz", "ZZ"); //$NON-NLS-1$//$NON-NLS-2$ - public static IArtifactDescriptor createArtifactDescriptor(IArtifactKey key, File pathOnDisk, boolean asIs, boolean recur) { + public static IArtifactDescriptor createArtifactDescriptor(IArtifactKey key, File pathOnDisk) { //TODO this size calculation is bogus ArtifactDescriptor result = new ArtifactDescriptor(key); if (pathOnDisk != null) { @@ -189,23 +192,16 @@ public class MetadataGeneratorHelper { return MetadataFactory.createInstallableUnit(cu); } - public static IInstallableUnit createBundleIU(BundleDescription bd, Map manifest, boolean isFolderPlugin, IArtifactKey key, Set localizationIUs) { - IInstallableUnit bundleIU = createBundleIU(bd, manifest, isFolderPlugin, key); - + public static IInstallableUnit createBundleIU(BundleDescription bd, Map manifest, boolean isFolderPlugin, IArtifactKey key) { + Map manifestLocalizations = null; if (manifest != null && bd.getLocation() != null) { - String bundleLocalization = (String) manifest.get(Constants.BUNDLE_LOCALIZATION); - if (bundleLocalization == null) { - bundleLocalization = DEFAULT_BUNDLE_LOCALIZATION; - } - Map manifestLocalizations = getManifestLocalizations(manifest, new File(bd.getLocation())); - if (manifestLocalizations != null) { - localizationIUs.addAll(createLocalizationFragmentsForBundle(bd, manifestLocalizations)); - } + manifestLocalizations = getManifestLocalizations(manifest, new File(bd.getLocation())); } - return bundleIU; + + return createBundleIU(bd, manifest, isFolderPlugin, key, manifestLocalizations); } - public static IInstallableUnit createBundleIU(BundleDescription bd, Map manifest, boolean isFolderPlugin, IArtifactKey key) { + public static IInstallableUnit createBundleIU(BundleDescription bd, Map manifest, boolean isFolderPlugin, IArtifactKey key, Map manifestLocalizations) { boolean isBinaryBundle = true; if (manifest != null && manifest.containsKey("Eclipse-SourceBundle")) { //$NON-NLS-1$ isBinaryBundle = false; @@ -266,6 +262,20 @@ public class MetadataGeneratorHelper { if (isFragment) providedCapabilities.add(MetadataFactory.createProvidedCapability(CAPABILITY_NS_OSGI_FRAGMENT, bd.getHost().getName(), bd.getVersion())); + + if (manifestLocalizations != null) { + for (Iterator iter = manifestLocalizations.keySet().iterator(); iter.hasNext();) { + Locale locale = (Locale) iter.next(); + Properties translatedStrings = (Properties) manifestLocalizations.get(locale); + Enumeration propertyKeys = translatedStrings.propertyNames(); + while (propertyKeys.hasMoreElements()) { + String nextKey = (String) propertyKeys.nextElement(); + iu.setProperty(locale.toString() + '.' + nextKey, translatedStrings.getProperty(nextKey)); + } + providedCapabilities.add(makeTranslationCapability(bd.getSymbolicName(), locale)); + } + } + iu.setCapabilities((ProvidedCapability[]) providedCapabilities.toArray(new ProvidedCapability[providedCapabilities.size()])); iu.setArtifacts(new IArtifactKey[] {key}); @@ -300,56 +310,11 @@ public class MetadataGeneratorHelper { return MetadataFactory.createInstallableUnit(iu); } - private static List createLocalizationFragmentsForBundle(BundleDescription bd, Map manifestLocalizations) { - List localizationFragments = new ArrayList(manifestLocalizations.size()); - for (Iterator iter = manifestLocalizations.keySet().iterator(); iter.hasNext();) { - Locale locale = (Locale) iter.next(); - Properties localizedStrings = (Properties) manifestLocalizations.get(locale); - IInstallableUnitFragment nextLocaleFragment = createLocalizationFragmentOfBundle(bd, locale, localizedStrings); - localizationFragments.add(nextLocaleFragment); - } - return localizationFragments; - } - - /* - * @param bd - * @param locale - * @param localizedStrings - * @return installableUnitFragment - */ - private static IInstallableUnitFragment createLocalizationFragmentOfBundle(BundleDescription bd, Locale locale, Properties localizedStrings) { - InstallableUnitFragmentDescription fragment = new MetadataFactory.InstallableUnitFragmentDescription(); - String fragmentId = makeLocalizationFragmentId(bd.getSymbolicName(), locale); - fragment.setId(fragmentId); - fragment.setVersion(bd.getVersion()); - - RequiredCapability[] hostReqs = new RequiredCapability[] {MetadataFactory.createRequiredCapability(NAMESPACE_ECLIPSE_TYPE, bd.getSymbolicName(), new VersionRange(bd.getVersion(), true, bd.getVersion(), true), null, false, false)}; - fragment.setHost(hostReqs); - - fragment.setSingleton(true); - - Enumeration propertyKeys = localizedStrings.propertyNames(); - while (propertyKeys.hasMoreElements()) { - String nextKey = (String) propertyKeys.nextElement(); - fragment.setProperty(nextKey, localizedStrings.getProperty(nextKey)); - } - // TODO: do we need any capabilities? - // Create set of provided capabilities It's just a tag indicating a localization fragment. - ArrayList providedCapabilities = new ArrayList(1); - providedCapabilities.add(MANIFEST_LOCALIZATION_CAPABILITY); - fragment.setCapabilities((ProvidedCapability[]) providedCapabilities.toArray(new ProvidedCapability[providedCapabilities.size()])); - - return MetadataFactory.createInstallableUnitFragment(fragment); - } - - public static void createHostLocalizationFragments(BundleDescription bd, String hostId, String[] hostBundleManifestValues, Set localizationIUs) { + public static void createHostLocalizationFragment(IInstallableUnit bundleIU, BundleDescription bd, String hostId, String[] hostBundleManifestValues, Set localizationIUs) { Map hostLocalizations = getHostLocalizations(new File(bd.getLocation()), hostBundleManifestValues); - - for (Iterator iter = hostLocalizations.keySet().iterator(); iter.hasNext();) { - Locale locale = (Locale) iter.next(); - Properties localizedStrings = (Properties) hostLocalizations.get(locale); - IInstallableUnitFragment nextLocaleFragment = createLocalizationFragmentOfHost(hostId, hostBundleManifestValues, bd, locale, localizedStrings); - localizationIUs.add(nextLocaleFragment); + if (hostLocalizations != null) { + IInstallableUnitFragment localizationFragment = createLocalizationFragmentOfHost(bd, hostId, hostBundleManifestValues, hostLocalizations); + localizationIUs.add(localizationFragment); } } @@ -360,29 +325,32 @@ public class MetadataGeneratorHelper { * @param localizedStrings * @return installableUnitFragment */ - private static IInstallableUnitFragment createLocalizationFragmentOfHost(String hostId, String[] hostManifestValues, BundleDescription bd, Locale locale, Properties localizedStrings) { + private static IInstallableUnitFragment createLocalizationFragmentOfHost(BundleDescription bd, String hostId, String[] hostManifestValues, Map hostLocalizations) { InstallableUnitFragmentDescription fragment = new MetadataFactory.InstallableUnitFragmentDescription(); - HostSpecification hostSpec = bd.getHost(); - String fragmentId = makeLocalizationFragmentId(hostId, locale); + String fragmentId = makeHostLocalizationFragmentId(bd.getSymbolicName()); fragment.setId(fragmentId); fragment.setVersion(bd.getVersion()); // TODO: is this a meaningful version? - RequiredCapability[] hostReqs = new RequiredCapability[] {MetadataFactory.createRequiredCapability(NAMESPACE_ECLIPSE_TYPE, hostSpec.getName(), hostSpec.getVersionRange(), null, false, false)}; + HostSpecification hostSpec = bd.getHost(); + RequiredCapability[] hostReqs = new RequiredCapability[] {MetadataFactory.createRequiredCapability(IInstallableUnit.NAMESPACE_IU_ID, hostSpec.getName(), hostSpec.getVersionRange(), null, false, false, false)}; fragment.setHost(hostReqs); fragment.setSingleton(true); + fragment.setProperty(IInstallableUnit.PROP_TYPE_FRAGMENT, Boolean.TRUE.toString()); - for (int i = 0; i < BUNDLE_LOCALIZED_PROPERTIES.length; i++) { - String nextKey = hostManifestValues[i]; - String localizedValue = null; - if (nextKey != null && (localizedValue = localizedStrings.getProperty(nextKey)) != null) { - fragment.setProperty(nextKey, localizedValue); + // Create a provided capability for each locale and add the translated properties. + ArrayList providedCapabilities = new ArrayList(hostLocalizations.keySet().size()); + for (Iterator iter = hostLocalizations.keySet().iterator(); iter.hasNext();) { + Locale locale = (Locale) iter.next(); + Properties translatedStrings = (Properties) hostLocalizations.get(locale); + + Enumeration propertyKeys = translatedStrings.propertyNames(); + while (propertyKeys.hasMoreElements()) { + String nextKey = (String) propertyKeys.nextElement(); + fragment.setProperty(locale.toString() + '.' + nextKey, translatedStrings.getProperty(nextKey)); } + providedCapabilities.add(makeTranslationCapability(hostId, locale)); } - // TODO: do we need any capabilities? It's just a tag indicating a localization fragment. - // Create set of provided capabilities - ArrayList providedCapabilities = new ArrayList(1); - providedCapabilities.add(MANIFEST_LOCALIZATION_CAPABILITY); fragment.setCapabilities((ProvidedCapability[]) providedCapabilities.toArray(new ProvidedCapability[providedCapabilities.size()])); return MetadataFactory.createInstallableUnitFragment(fragment); @@ -390,14 +358,23 @@ public class MetadataGeneratorHelper { /** * @param id - * @param locale - * @return the id for the fragment contain the localized properties - * for the manifest of the bundle with the given id - * in the given locale. + * @return the id for the iu fragment containing the localized properties + * for the bundle with the given id */ - private static String makeLocalizationFragmentId(String id, Locale locale) { - String localeString = (!DEFAULT_LOCALE.equals(locale) ? '_' + locale.toString() : ""); //$NON-NLS-1$ - return id + "_manifest" + localeString + "_properties"; //$NON-NLS-1$ //$NON-NLS-2$ + // private static String makeBundleLocalizationFragmentId(String id) { + // return id + ".translated_properties"; //$NON-NLS-1$ + // } + /** + * @param id + * @return the id for the iu fragment containing localized properties + * for the fragment with the given id. + */ + private static String makeHostLocalizationFragmentId(String id) { + return id + ".translated_host_properties"; //$NON-NLS-1$ + } + + private static ProvidedCapability makeTranslationCapability(String hostId, Locale locale) { + return MetadataFactory.createProvidedCapability(NAMESPACE_IU_LOCALIZATION, locale.toString(), new Version(1, 0, 0)); } /** @@ -546,41 +523,25 @@ public class MetadataGeneratorHelper { } public static IInstallableUnit[] createEclipseIU(BundleDescription bd, Map manifest, boolean isFolderPlugin, IArtifactKey key, Properties extraProperties) { - ArrayList iusCreated = new ArrayList(4); + ArrayList iusCreated = new ArrayList(1); IInstallableUnit iu = createBundleIU(bd, manifest, isFolderPlugin, key); addExtraProperties(iu, extraProperties); iusCreated.add(iu); - if (manifest != null) { - String bundleLocalization = null; - if (bd.getHost() == null) // not a fragment - bundleLocalization = (String) manifest.get(Constants.BUNDLE_LOCALIZATION); - if (bundleLocalization == null) - bundleLocalization = DEFAULT_BUNDLE_LOCALIZATION; - - Map manifestLocalizations = getManifestLocalizations(manifest, new File(bd.getLocation())); - - if (manifestLocalizations != null) { - List localizationFragments = createLocalizationFragmentsForBundle(bd, manifestLocalizations); - for (Iterator iter = localizationFragments.iterator(); iter.hasNext();) { - addExtraProperties((IInstallableUnit) iter.next(), extraProperties); - } - iusCreated.addAll(localizationFragments); - } - } - return (IInstallableUnit[]) (iusCreated.toArray(new IInstallableUnit[iusCreated.size()])); } + /** + * @deprecated + */ public static IArtifactKey createFeatureArtifactKey(String fsn, String version) { return new ArtifactKey(ECLIPSE_FEATURE_CLASSIFIER, fsn, new Version(version)); } - public static IInstallableUnit createFeatureJarIU(Feature feature, boolean isExploded) { - return createFeatureJarIU(feature, null, isExploded, null); - } - + /** + * @deprecated + */ public static IInstallableUnit createFeatureJarIU(Feature feature, ArrayList childIUs, boolean isExploded, Properties extraProperties) { InstallableUnitDescription iu = new MetadataFactory.InstallableUnitDescription(); String id = getTransformedId(feature.getId(), /*isPlugin*/false, /*isGroup*/false); @@ -643,12 +604,16 @@ public class MetadataGeneratorHelper { return MetadataFactory.createInstallableUnit(iu); } - // moved to FeaturesAction + /** + * @deprecated moved to FeaturesAction + */ public static IInstallableUnit createGroupIU(Feature feature, IInstallableUnit featureIU) { return createGroupIU(feature, featureIU, null); } - // moved to FeaturesAction + /** + * @deprecated moved to FeaturesAction + */ public static IInstallableUnit createGroupIU(Feature feature, IInstallableUnit featureIU, Properties extraProperties) { InstallableUnitDescription iu = new MetadataFactory.InstallableUnitDescription(); String id = getTransformedId(feature.getId(), /*isPlugin*/false, /*isGroup*/true); @@ -734,7 +699,7 @@ public class MetadataGeneratorHelper { results.addIU(MetadataFactory.createInstallableUnit(cu), IPublisherResult.ROOT); //Create the artifact descriptor - return createArtifactDescriptor(key, jreLocation, false, true); + return createArtifactDescriptor(key, jreLocation); } public static ArtifactKey createLauncherArtifactKey(String id, Version version) { @@ -792,7 +757,7 @@ public class MetadataGeneratorHelper { resultantIUs.addIU(MetadataFactory.createInstallableUnitFragment(cu), IPublisherResult.ROOT); //Create the artifact descriptor - return createArtifactDescriptor(key, launcher, false, true); + return createArtifactDescriptor(key, launcher); } public static void generateLauncherSetter(String launcherName, String iuId, Version version, String configSpec, IPublisherResult result) { @@ -934,8 +899,11 @@ public class MetadataGeneratorHelper { // TODO should really be returning VersionRange.emptyRange here... return null; Version version = new Version(versionSpec); - if (!entry.isRequires()) + if (!entry.isRequires()) { + if ("0.0.0".equals(entry.getVersion())) //$NON-NLS-1$ + return VersionRange.emptyRange; return new VersionRange(version, true, version, true); + } String match = entry.getMatch(); if (match == null) // TODO should really be returning VersionRange.emptyRange here... diff --git a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/Publisher.java b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/Publisher.java index b84d08a14..ec9b7878f 100644 --- a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/Publisher.java +++ b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/Publisher.java @@ -9,16 +9,117 @@ ******************************************************************************/ package org.eclipse.equinox.internal.p2.publisher; +import java.net.MalformedURLException; +import java.net.URL; import java.util.Collection; import org.eclipse.core.runtime.*; +import org.eclipse.equinox.internal.p2.core.helpers.ServiceHelper; +import org.eclipse.equinox.internal.provisional.p2.artifact.repository.IArtifactRepository; +import org.eclipse.equinox.internal.provisional.p2.artifact.repository.IArtifactRepositoryManager; +import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException; +import org.eclipse.equinox.internal.provisional.p2.core.repository.IRepository; import org.eclipse.equinox.internal.provisional.p2.metadata.IInstallableUnit; import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepository; +import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepositoryManager; +import org.eclipse.osgi.util.NLS; public class Publisher { + static final public String PUBLISH_PACK_FILES_AS_SIBLINGS = "publishPackFilesAsSiblings"; //$NON-NLS-1$ private IPublisherInfo info; private IPublisherResult results; + /** + * Returns a metadata repository that corresponds to the given settings. If a repo at the + * given location already exists, it is updated with the settings and returned. If no repository + * is found then a new Simple repository is created, configured and returned + * @param location the URL location of the repo + * @param name the name of the repo + * @param append whether or not the repo should appended or cleared + * @param compress whether or not to compress the repository index + * @return the discovered or created repository + * @throws ProvisionException + */ + public static IMetadataRepository createMetadataRepository(String location, String name, boolean append, boolean compress) throws ProvisionException { + URL url; + try { + url = new URL(location); + } catch (MalformedURLException e) { + throw new IllegalArgumentException(NLS.bind(Messages.exception_metadataRepoLocationURL, location)); + } + IMetadataRepositoryManager manager = (IMetadataRepositoryManager) ServiceHelper.getService(Activator.context, IMetadataRepositoryManager.class.getName()); + try { + IMetadataRepository result = manager.loadRepository(url, null); + if (result != null) { + result.setProperty(IRepository.PROP_COMPRESSED, compress ? "true" : "false"); //$NON-NLS-1$//$NON-NLS-2$ + if (!result.isModifiable()) + throw new IllegalArgumentException(NLS.bind(Messages.exception_metadataRepoNotWritable, url)); + if (!append) + result.removeAll(); + return result; + } + } catch (ProvisionException e) { + //fall through and create a new repository + } + + // the given repo location is not an existing repo so we have to create something + // TODO for now create a random repo by default. + String repositoryName = name == null ? location + " - metadata" : name; //$NON-NLS-1$ + IMetadataRepository result = manager.createRepository(url, repositoryName, IMetadataRepositoryManager.TYPE_SIMPLE_REPOSITORY); + manager.addRepository(result.getLocation()); + if (result != null) + result.setProperty(IRepository.PROP_COMPRESSED, compress ? "true" : "false"); //$NON-NLS-1$//$NON-NLS-2$ + return result; + } + + /** + * Returns an artifact repository that corresponds to the given settings. If a repo at the + * given location already exists, it is updated with the settings and returned. If no repository + * is found then a new Simple repository is created, configured and returned + * @param location the URL location of the repo + * @param name the name of the repo + * @param append whether or not the repo should appended or cleared + * @param compress whether or not to compress the repository index + * @param reusePackedFiles whether or not to include discovered Pack200 files in the repository + * @return the discovered or created repository + * @throws ProvisionException + */ + public static IArtifactRepository createArtifactRepository(String location, String name, boolean append, boolean compress, boolean reusePackedFiles) throws ProvisionException { + IArtifactRepositoryManager manager = (IArtifactRepositoryManager) ServiceHelper.getService(Activator.context, IArtifactRepositoryManager.class.getName()); + URL url; + try { + url = new URL(location); + } catch (MalformedURLException e) { + throw new IllegalArgumentException(NLS.bind(Messages.exception_artifactRepoLocationURL, location)); + } + try { + IArtifactRepository result = manager.loadRepository(url, null); + if (!result.isModifiable()) + throw new IllegalArgumentException(NLS.bind(Messages.exception_artifactRepoNotWritable, url)); + result.setProperty(IRepository.PROP_COMPRESSED, compress ? "true" : "false"); //$NON-NLS-1$//$NON-NLS-2$ + if (reusePackedFiles) + result.setProperty(PUBLISH_PACK_FILES_AS_SIBLINGS, "true"); //$NON-NLS-1$ + if (!append) + result.removeAll(); + return result; + } catch (ProvisionException e) { + //fall through and create a new repository + } + + // the given repo location is not an existing repo so we have to create something + // TODO for now create a Simple repo by default. + String repositoryName = name != null ? name : location + " - artifacts"; //$NON-NLS-1$ + IArtifactRepository result = manager.createRepository(url, repositoryName, IArtifactRepositoryManager.TYPE_SIMPLE_REPOSITORY); + manager.addRepository(result.getLocation()); + // TODO there must be something we have to do to set up the mapping rules here... + // if (inplace) { + // } + result.setProperty(IRepository.PROP_COMPRESSED, compress ? "true" : "false"); //$NON-NLS-1$//$NON-NLS-2$ + if (reusePackedFiles) + result.setProperty(PUBLISH_PACK_FILES_AS_SIBLINGS, "true"); //$NON-NLS-1$ + return result; + } + public Publisher(IPublisherInfo info) { this.info = info; results = new PublisherResult(); diff --git a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/PublisherResult.java b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/PublisherResult.java index 327f502a7..92c833037 100644 --- a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/PublisherResult.java +++ b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/PublisherResult.java @@ -31,23 +31,17 @@ public class PublisherResult implements IPublisherResult { } public void addIUs(Collection ius, String type) { - for (Iterator i = ius.iterator(); i.hasNext();) { - IInstallableUnit iu = (IInstallableUnit) i.next(); - addIU(iu, type); - } + for (Iterator i = ius.iterator(); i.hasNext();) + addIU((IInstallableUnit) i.next(), type); } private void addIU(Map map, String id, IInstallableUnit iu) { - IInstallableUnit[] ius = (IInstallableUnit[]) map.get(id); + Set ius = (Set) map.get(id); if (ius == null) { - ius = new IInstallableUnit[] {iu}; + ius = new HashSet(11); map.put(id, ius); - } else { - IInstallableUnit[] newIUs = new IInstallableUnit[ius.length + 1]; - System.arraycopy(ius, 0, newIUs, 0, ius.length); - newIUs[ius.length] = iu; - map.put(id, newIUs); } + ius.add(iu); } /** @@ -71,14 +65,14 @@ public class PublisherResult implements IPublisherResult { // matching IU non-deterministically. public IInstallableUnit getIU(String id, String type) { if (type == null || type == ROOT) { - IInstallableUnit[] ius = (IInstallableUnit[]) rootIUs.get(id); - if (ius != null && ius.length > 0) - return ius[0]; + Collection ius = (Collection) rootIUs.get(id); + if (ius != null && ius.size() > 0) + return (IInstallableUnit) ius.iterator().next(); } if (type == null || type == NON_ROOT) { - IInstallableUnit[] ius = (IInstallableUnit[]) nonRootIUs.get(id); - if (ius != null && ius.length > 0) - return ius[0]; + Collection ius = (Collection) nonRootIUs.get(id); + if (ius != null && ius.size() > 0) + return (IInstallableUnit) ius.iterator().next(); } return null; } @@ -89,14 +83,14 @@ public class PublisherResult implements IPublisherResult { public Collection getIUs(String id, String type) { if (type == null) { ArrayList result = new ArrayList(); - result.addAll(id == null ? flatten(rootIUs.values()) : Arrays.asList((Object[]) rootIUs.get(id))); - result.addAll(id == null ? flatten(nonRootIUs.values()) : Arrays.asList((Object[]) nonRootIUs.get(id))); + result.addAll(id == null ? flatten(rootIUs.values()) : (Collection) rootIUs.get(id)); + result.addAll(id == null ? flatten(nonRootIUs.values()) : (Collection) nonRootIUs.get(id)); return result; } if (type == ROOT) - return id == null ? flatten(rootIUs.values()) : Arrays.asList((Object[]) rootIUs.get(id)); + return id == null ? flatten(rootIUs.values()) : (Collection) rootIUs.get(id); if (type == NON_ROOT) - return id == null ? flatten(nonRootIUs.values()) : Arrays.asList((Object[]) nonRootIUs.get(id)); + return id == null ? flatten(nonRootIUs.values()) : (Collection) nonRootIUs.get(id); return null; } @@ -111,7 +105,6 @@ public class PublisherResult implements IPublisherResult { } public void merge(IPublisherResult result, int mode) { - if (mode == MERGE_MATCHING) { addIUs(result.getIUs(null, ROOT), ROOT); addIUs(result.getIUs(null, NON_ROOT), NON_ROOT); diff --git a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/AccumulateConfigDataAction.java b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/AccumulateConfigDataAction.java index 872bbe63c..95cde20d4 100644 --- a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/AccumulateConfigDataAction.java +++ b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/AccumulateConfigDataAction.java @@ -37,6 +37,8 @@ public class AccumulateConfigDataAction extends AbstractPublishingAction { return; info.addAdvice(new ConfigAdvice(data, configSpec)); LauncherData launcherData = loader.getLauncherData(); + if (launcherData == null) + return; info.addAdvice(new LaunchingAdvice(launcherData, configSpec)); } } diff --git a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/BundlesAction.java b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/BundlesAction.java index 205f499a6..e61a7e2c5 100644 --- a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/BundlesAction.java +++ b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/BundlesAction.java @@ -16,8 +16,7 @@ import java.util.*; import org.eclipse.core.runtime.*; import org.eclipse.equinox.internal.p2.core.helpers.ServiceHelper; import org.eclipse.equinox.internal.p2.publisher.*; -import org.eclipse.equinox.internal.provisional.p2.artifact.repository.IArtifactDescriptor; -import org.eclipse.equinox.internal.provisional.p2.artifact.repository.IArtifactRepository; +import org.eclipse.equinox.internal.provisional.p2.artifact.repository.*; import org.eclipse.equinox.internal.provisional.p2.metadata.IArtifactKey; import org.eclipse.equinox.internal.provisional.p2.metadata.IInstallableUnit; import org.eclipse.osgi.service.resolver.*; @@ -49,20 +48,26 @@ public class BundlesAction extends AbstractPublishingAction { } private File[] expandLocations(File[] list) { - if (list == null) - return new File[] {}; ArrayList result = new ArrayList(); + expandLocations(list, result); + return (File[]) result.toArray(new File[result.size()]); + } + + private void expandLocations(File[] list, ArrayList result) { + if (list == null) + return; for (int i = 0; i < list.length; i++) { File location = list[i]; if (location.isDirectory()) { - File[] entries = location.listFiles(); - for (int j = 0; j < entries.length; j++) - result.add(entries[j]); + // if the location is itself a bundle, just add it. Otherwise r down + if (!new File(location, "META-INF/MANIFEST.MF").exists()) //$NON-NLS-1$ + expandLocations(location.listFiles(), result); + else + result.add(location); } else { result.add(location); } } - return (File[]) result.toArray(new File[result.size()]); } protected void generateBundleIUs(BundleDescription[] bundles, IPublisherResult result, IPublisherInfo info) { @@ -72,7 +77,6 @@ public class BundlesAction extends AbstractPublishingAction { // from the manifest for the localizable properties. final int CACHE_PHASE = 0; final int GENERATE_PHASE = 1; - final int BUNDLE_LOCALIZATION_INDEX = MetadataGeneratorHelper.BUNDLE_LOCALIZATION_INDEX; Map bundleLocalizationMap = new HashMap(bundles.length); Set localizationIUs = new HashSet(32); for (int phase = CACHE_PHASE; phase <= GENERATE_PHASE; phase++) { @@ -90,15 +94,18 @@ public class BundlesAction extends AbstractPublishingAction { } } else { IArtifactKey key = MetadataGeneratorHelper.createBundleArtifactKey(bd.getSymbolicName(), bd.getVersion().toString()); - IArtifactDescriptor ad = MetadataGeneratorHelper.createArtifactDescriptor(key, new File(bd.getLocation()), true, false); + File location = new File(bd.getLocation()); + IArtifactDescriptor ad = MetadataGeneratorHelper.createArtifactDescriptor(key, location); + addProperties((ArtifactDescriptor) ad, location, info); IArtifactRepository destination = info.getArtifactRepository(); // don't consider any advice here as we want to know about the real form on disk boolean isDir = isDir(bd, info); - if (isDir) + // if the artifact is a dir and we are not doing "AS_IS", zip it up. + if (isDir && !((info.getArtifactOptions() & IPublisherInfo.A_AS_IS) > 0)) publishArtifact(ad, new File(bd.getLocation()), new File(bd.getLocation()).listFiles(), info, INCLUDE_ROOT); else publishArtifact(ad, new File(bd.getLocation()), new File[] {new File(bd.getLocation())}, info, AS_IS | INCLUDE_ROOT); - IInstallableUnit bundleIU = MetadataGeneratorHelper.createBundleIU(bd, bundleManifest, isDir, key, localizationIUs); + IInstallableUnit bundleIU = MetadataGeneratorHelper.createBundleIU(bd, bundleManifest, isDir, key); if (isFragment(bd)) { // TODO: Can NL fragments be multi-host? What special handling @@ -108,7 +115,7 @@ public class BundlesAction extends AbstractPublishingAction { String[] cachedValues = (String[]) bundleLocalizationMap.get(hostKey); if (cachedValues != null) { - MetadataGeneratorHelper.createHostLocalizationFragments(bd, hostId, cachedValues, localizationIUs); + MetadataGeneratorHelper.createHostLocalizationFragment(bundleIU, bd, hostId, cachedValues, localizationIUs); } } @@ -121,6 +128,26 @@ public class BundlesAction extends AbstractPublishingAction { } } + /** + * Add all of the advice for the bundle at the given location to the given descriptor. + * @param descriptor the descriptor to decorate + * @param location the location of the bundle + * @param info the publisher info supplying the advice + */ + private void addProperties(ArtifactDescriptor descriptor, File location, IPublisherInfo info) { + Collection advice = info.getAdvice(null, false, null, null, IBundleAdvice.class); + for (Iterator i = advice.iterator(); i.hasNext();) { + IBundleAdvice entry = (IBundleAdvice) i.next(); + Properties props = entry.getProperties(location); + if (props == null) + continue; + for (Iterator j = props.keySet().iterator(); j.hasNext();) { + String key = (String) j.next(); + descriptor.setRepositoryProperty(key, props.getProperty(key)); + } + } + } + private boolean isDir(BundleDescription bundle, IPublisherInfo info) { Collection advice = info.getAdvice(null, true, bundle.getSymbolicName(), bundle.getVersion(), IBundleShapeAdvice.class); // if the advice has a shape, use it diff --git a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/ConfigCUsAction.java b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/ConfigCUsAction.java index e19a287be..225f4b1c7 100644 --- a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/ConfigCUsAction.java +++ b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/ConfigCUsAction.java @@ -19,7 +19,10 @@ import org.eclipse.core.runtime.Status; import org.eclipse.equinox.internal.p2.publisher.*; import org.eclipse.equinox.internal.provisional.frameworkadmin.BundleInfo; import org.eclipse.equinox.internal.provisional.p2.metadata.IInstallableUnit; +import org.eclipse.equinox.internal.provisional.p2.metadata.query.InstallableUnitQuery; import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepository; +import org.eclipse.equinox.internal.provisional.p2.query.Collector; +import org.eclipse.equinox.internal.provisional.p2.query.Query; import org.eclipse.osgi.util.ManifestElement; import org.osgi.framework.*; @@ -176,7 +179,7 @@ public class ConfigCUsAction extends AbstractPublishingAction { } for (int i = 0; i < bundles.length; i++) { - GeneratorBundleInfo bundle = createGeneratorBundleInfo(bundles[i], result); + GeneratorBundleInfo bundle = createGeneratorBundleInfo(info, bundles[i], result); if (bundle == null) continue; @@ -202,7 +205,7 @@ public class ConfigCUsAction extends AbstractPublishingAction { } } - protected GeneratorBundleInfo createGeneratorBundleInfo(BundleInfo bundleInfo, IPublisherResult result) { + protected GeneratorBundleInfo createGeneratorBundleInfo(IPublisherInfo info, BundleInfo bundleInfo, IPublisherResult result) { if (bundleInfo.getLocation() != null) return new GeneratorBundleInfo(bundleInfo); @@ -230,6 +233,22 @@ public class ConfigCUsAction extends AbstractPublishingAction { } } + //Query the repo + Query query = new InstallableUnitQuery(name); + Collector collector = new Collector(); + Iterator matches = info.getMetadataRepository().query(query, collector, null).iterator(); + //pick the newest match + IInstallableUnit newest = null; + while (matches.hasNext()) { + IInstallableUnit candidate = (IInstallableUnit) matches.next(); + if (newest == null || (newest.getVersion().compareTo(candidate.getVersion()) < 0)) + newest = candidate; + } + if (newest != null) { + bundleInfo.setVersion(newest.getVersion().toString()); + return new GeneratorBundleInfo(bundleInfo); + } + return null; } diff --git a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/EclipseInstallAction.java b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/EclipseInstallAction.java index c4ec7be9d..5710edb78 100644 --- a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/EclipseInstallAction.java +++ b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/EclipseInstallAction.java @@ -112,7 +112,7 @@ public class EclipseInstallAction implements IPublishingAction { } protected IPublishingAction createFeaturesAction() { - return new FeaturesAction(new File[] {new File(source, "features")}, info); //$NON-NLS-1$ + return new FeaturesAction(new File[] {new File(source, "features")}); //$NON-NLS-1$ } protected Collection createExecutablesActions(String[] configSpecs) { diff --git a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/EquinoxExecutableAction.java b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/EquinoxExecutableAction.java index e54faccc3..6e3a29181 100644 --- a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/EquinoxExecutableAction.java +++ b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/EquinoxExecutableAction.java @@ -127,7 +127,7 @@ public class EquinoxExecutableAction extends AbstractPublishingAction { result.addIU(unit, IPublisherResult.ROOT); //Create the artifact descriptor. we have several files so no path on disk - IArtifactDescriptor descriptor = MetadataGeneratorHelper.createArtifactDescriptor(key, null, false, true); + IArtifactDescriptor descriptor = MetadataGeneratorHelper.createArtifactDescriptor(key, null); publishArtifact(descriptor, fileList, files.getLocation(), info, INCLUDE_ROOT); if (files.isTemporary()) FileUtils.deleteAll(files.getLocation()); diff --git a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/FeaturesAction.java b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/FeaturesAction.java index 04e0dfdae..d1bd1e37e 100644 --- a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/FeaturesAction.java +++ b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/FeaturesAction.java @@ -15,6 +15,7 @@ import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.equinox.internal.p2.core.helpers.FileUtils; import org.eclipse.equinox.internal.p2.core.helpers.LogHelper; +import org.eclipse.equinox.internal.p2.metadata.ArtifactKey; import org.eclipse.equinox.internal.p2.publisher.*; import org.eclipse.equinox.internal.p2.publisher.features.*; import org.eclipse.equinox.internal.provisional.p2.artifact.repository.ArtifactDescriptor; @@ -29,76 +30,109 @@ import org.osgi.framework.Version; * be actual locations of the features or folders of features. */ public class FeaturesAction extends AbstractPublishingAction { - public static final String INSTALL_FEATURES_FILTER = "(org.eclipse.update.install.features=true)"; //$NON-NLS-1$ - private File[] locations; + protected Feature[] featureList; public static String getTransformedId(String original, boolean isPlugin, boolean isGroup) { return (isPlugin ? original : original + (isGroup ? ".feature.group" : ".feature.jar")); //$NON-NLS-1$//$NON-NLS-2$ } - public FeaturesAction(File[] locations, IPublisherInfo info) { - this.locations = expandLocations(locations); + public static IArtifactKey createFeatureArtifactKey(String id, String version) { + return new ArtifactKey(MetadataGeneratorHelper.ECLIPSE_FEATURE_CLASSIFIER, id, new Version(version)); + } + + public FeaturesAction(File[] locations) { + featureList = getFeatures(expandLocations(locations)); + } + + public FeaturesAction(Feature[] featureList) { + this.featureList = featureList; } public IStatus perform(IPublisherInfo info, IPublisherResult results) { - Feature[] features = getFeatures(locations); - generateFeatureIUs(features, results, info); + generateFeatureIUs(featureList, results, info); return Status.OK_STATUS; } private File[] expandLocations(File[] list) { - if (list == null) - return new File[] {}; ArrayList result = new ArrayList(); + expandLocations(list, result); + return (File[]) result.toArray(new File[result.size()]); + } + + private void expandLocations(File[] list, ArrayList result) { + if (list == null) + return; for (int i = 0; i < list.length; i++) { File location = list[i]; if (location.isDirectory()) { - File[] entries = location.listFiles(); - for (int j = 0; j < entries.length; j++) - result.add(entries[j]); + // if the location is itself a feature, just add it. Otherwise r down + if (!new File(location, "feature.xml").exists()) + expandLocations(location.listFiles(), result); + else + result.add(location); } else { result.add(location); } } - return (File[]) result.toArray(new File[result.size()]); } protected void generateFeatureIUs(Feature[] features, IPublisherResult result, IPublisherInfo info) { // Build Feature IUs, and add them to any corresponding categories for (int i = 0; i < features.length; i++) { Feature feature = features[i]; - - // generate the root file IUs for this feature. The IU hierarchy must - // be built from the bottom up so do the root files first. + // The IU hierarchy must be built from the bottom up so do the root files first. ArrayList childIUs = generateRootFileIUs(feature, result, info); + IInstallableUnit featureIU = createFeatureJarIU(feature, childIUs, info); + publishFeatureArtifacts(feature, featureIU, info); + gatherBundleShapeAdvice(feature, info); + // create the associated group and register the feature and group in the result. + IInstallableUnit groupIU = createGroupIU(feature, featureIU, null); + result.addIU(groupIU, IPublisherResult.ROOT); + result.addIU(featureIU, IPublisherResult.ROOT); + } + } - // create the basic feature IU with all the children - String location = feature.getLocation(); - boolean isExploded = (location.endsWith(".jar") ? false : true); //$NON-NLS-1$ - IInstallableUnit featureIU = MetadataGeneratorHelper.createFeatureJarIU(feature, childIUs, isExploded, null); - - // add all the artifacts associated with the feature - IArtifactKey[] artifacts = featureIU.getArtifacts(); - for (int arti = 0; arti < artifacts.length; arti++) { - IArtifactDescriptor ad = MetadataGeneratorHelper.createArtifactDescriptor(artifacts[arti], new File(location), true, false); - if (isExploded) - publishArtifact(ad, new File(location).listFiles(), info, INCLUDE_ROOT); - else - publishArtifact(ad, new File[] {new File(location)}, info, AS_IS | INCLUDE_ROOT); - } + protected IInstallableUnit createFeatureJarIU(Feature feature, ArrayList childIUs, IPublisherInfo info) { + // create the basic feature IU with all the children + String location = feature.getLocation(); + boolean isExploded = (location.endsWith(".jar") ? false : true); //$NON-NLS-1$ + Properties props = getFeatureAdvice(feature, info); + IInstallableUnit featureIU = createFeatureJarIU(feature, childIUs, isExploded, props); + return featureIU; + } - gatherAdvice(feature, info); + protected void publishFeatureArtifacts(Feature feature, IInstallableUnit featureIU, IPublisherInfo info) { + // add all the artifacts associated with the feature + // TODO this is a little strange. If there are several artifacts, how do we know which files go with + // which artifacts when we publish them? For now it would be surprising to have more than one + // artifact per feature IU. + IArtifactKey[] artifacts = featureIU.getArtifacts(); + for (int j = 0; j < artifacts.length; j++) { + File file = new File(feature.getLocation()); + IArtifactDescriptor ad = MetadataGeneratorHelper.createArtifactDescriptor(artifacts[j], file); + // if the artifact is a dir and we are not doing "AS_IS", zip it up. + if (file.isDirectory() && !((info.getArtifactOptions() & IPublisherInfo.A_AS_IS) > 0)) + publishArtifact(ad, file.listFiles(), info, INCLUDE_ROOT); + else + publishArtifact(ad, new File[] {file}, info, AS_IS | INCLUDE_ROOT); + } + } - // create the associated group and register the feature and group in the result. - IInstallableUnit generated = createGroupIU(feature, featureIU, null); - result.addIU(generated, IPublisherResult.ROOT); - result.addIU(featureIU, IPublisherResult.ROOT); + private Properties getFeatureAdvice(Feature feature, IPublisherInfo info) { + Properties result = new Properties(); + Collection advice = info.getAdvice(null, false, null, null, IFeatureAdvice.class); + for (Iterator i = advice.iterator(); i.hasNext();) { + IFeatureAdvice entry = (IFeatureAdvice) i.next(); + Properties props = entry.getProperties(feature, null); + if (props != null) + result.putAll(props); } + return result; } - private ArrayList generateRootFileIUs(Feature feature, IPublisherResult result, IPublisherInfo info) { + protected ArrayList generateRootFileIUs(Feature feature, IPublisherResult result, IPublisherInfo info) { File location = new File(feature.getLocation()); Properties props = loadProperties(location, "build.properties"); return generateRootFileIUs(feature.getId(), feature.getVersion(), props, location, result, info); @@ -206,7 +240,7 @@ public class FeaturesAction extends AbstractPublishingAction { * @param feature the feature to process * @param info the publishing info to update */ - public void gatherAdvice(Feature feature, IPublisherInfo info) { + public void gatherBundleShapeAdvice(Feature feature, IPublisherInfo info) { FeatureEntry entries[] = feature.getEntries(); for (int i = 0; i < entries.length; i++) { FeatureEntry entry = entries[i]; @@ -215,6 +249,68 @@ public class FeaturesAction extends AbstractPublishingAction { } } + public IInstallableUnit createFeatureJarIU(Feature feature, ArrayList childIUs, boolean isExploded, Properties extraProperties) { + InstallableUnitDescription iu = new MetadataFactory.InstallableUnitDescription(); + String id = getTransformedId(feature.getId(), /*isPlugin*/false, /*isGroup*/false); + iu.setId(id); + Version version = new Version(feature.getVersion()); + iu.setVersion(version); + if (feature.getLicense() != null) + iu.setLicense(new License(feature.getLicenseURL(), feature.getLicense())); + if (feature.getCopyright() != null) + iu.setCopyright(new Copyright(feature.getCopyrightURL(), feature.getCopyright())); + + // The required capabilities are not specified at this level because we don't want the feature jar to be attractive to install. + + iu.setTouchpointType(MetadataGeneratorHelper.TOUCHPOINT_OSGI); + iu.setFilter(INSTALL_FEATURES_FILTER); + iu.setSingleton(true); + + if (feature.getInstallHandler() != null && feature.getInstallHandler().trim().length() > 0) { + String installHandlerProperty = "handler=" + feature.getInstallHandler(); //$NON-NLS-1$ + + if (feature.getInstallHandlerLibrary() != null) + installHandlerProperty += ", library=" + feature.getInstallHandlerLibrary(); //$NON-NLS-1$ + + if (feature.getInstallHandlerURL() != null) + installHandlerProperty += ", url=" + feature.getInstallHandlerURL(); //$NON-NLS-1$ + + iu.setProperty(MetadataGeneratorHelper.ECLIPSE_INSTALL_HANDLER_PROP, installHandlerProperty); + } + + iu.setCapabilities(new ProvidedCapability[] {MetadataGeneratorHelper.createSelfCapability(id, version), MetadataGeneratorHelper.FEATURE_CAPABILITY, MetadataFactory.createProvidedCapability(MetadataGeneratorHelper.CAPABILITY_NS_UPDATE_FEATURE, feature.getId(), version)}); + iu.setArtifacts(new IArtifactKey[] {createFeatureArtifactKey(feature.getId(), version.toString())}); + + // link in all the children (if any) as requirements. + // TODO consider if these should be linked as exact version numbers. Should be ok but may be brittle. + if (childIUs != null) { + RequiredCapability[] required = new RequiredCapability[childIUs.size()]; + for (int i = 0; i < childIUs.size(); i++) { + IInstallableUnit child = (IInstallableUnit) childIUs.get(i); + required[i] = MetadataFactory.createRequiredCapability(MetadataGeneratorHelper.IU_NAMESPACE, child.getId(), new VersionRange(child.getVersion(), true, child.getVersion(), true), INSTALL_FEATURES_FILTER, false, false); + } + iu.setRequiredCapabilities(required); + } + + if (isExploded) { + // Define the immutable metadata for this IU. In this case immutable means + // that this is something that will not impact the configuration. + Map touchpointData = new HashMap(); + touchpointData.put("zipped", "true"); //$NON-NLS-1$ //$NON-NLS-2$ + iu.addTouchpointData(MetadataFactory.createTouchpointData(touchpointData)); + } + + if (extraProperties != null) { + Enumeration e = extraProperties.propertyNames(); + while (e.hasMoreElements()) { + String name = (String) e.nextElement(); + iu.setProperty(name, extraProperties.getProperty(name)); + } + } + + return MetadataFactory.createInstallableUnit(iu); + } + public IInstallableUnit createGroupIU(Feature feature, IInstallableUnit featureIU, Properties extraProperties) { InstallableUnitDescription iu = new MetadataFactory.InstallableUnitDescription(); String id = getTransformedId(feature.getId(), /*isPlugin*/false, /*isGroup*/true); diff --git a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/IBundleAdvice.java b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/IBundleAdvice.java new file mode 100644 index 000000000..0f6aa6acb --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/IBundleAdvice.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2008 Code 9 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: + * Code 9 - initial API and implementation + ******************************************************************************/ +package org.eclipse.equinox.internal.p2.publisher.actions; + +import java.io.File; +import java.util.Properties; +import org.eclipse.equinox.internal.p2.publisher.IPublishingAdvice; + +public interface IBundleAdvice extends IPublishingAdvice { + + /** + * Returns the set of extra properties to be associated with the IU for the bundle + * at the given location + * @param location the location of the feature to advise + * @return extra properties for the given feature + */ + public Properties getProperties(File location); +} diff --git a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/IFeatureAdvice.java b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/IFeatureAdvice.java new file mode 100644 index 000000000..d13a82db1 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/IFeatureAdvice.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2008 Code 9 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: + * Code 9 - initial API and implementation + ******************************************************************************/ +package org.eclipse.equinox.internal.p2.publisher.actions; + +import java.io.File; +import java.util.Properties; +import org.eclipse.equinox.internal.p2.publisher.IPublishingAdvice; +import org.eclipse.equinox.internal.p2.publisher.features.Feature; + +public interface IFeatureAdvice extends IPublishingAdvice { + + /** + * Returns the set of extra properties to be associated with the IU for the feature + * at the given location + * @param location the location of the feature to advise + * @return extra properties for the given feature + */ + public Properties getProperties(Feature feature, File location); +} diff --git a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/LocalUpdateSiteAction.java b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/LocalUpdateSiteAction.java new file mode 100644 index 000000000..8f8bf1403 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/LocalUpdateSiteAction.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2008 Code 9 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: + * Code 9 - initial API and implementation + ******************************************************************************/ +package org.eclipse.equinox.internal.p2.publisher.actions; + +import java.io.File; +import java.net.MalformedURLException; +import java.util.ArrayList; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.equinox.internal.p2.publisher.*; +import org.eclipse.equinox.internal.p2.publisher.features.UpdateSite; + +/** + * A publishing action that processes a local (File-based) update site and generates + * metadata and artifacts for the features, bundles and site index (categories etc). + */ +public class LocalUpdateSiteAction implements IPublishingAction { + protected String source; + private UpdateSite updateSite; + + protected LocalUpdateSiteAction() { + } + + public LocalUpdateSiteAction(String source) { + this.source = source; + } + + public LocalUpdateSiteAction(UpdateSite updateSite) { + this.updateSite = updateSite; + } + + public IStatus perform(IPublisherInfo info, IPublisherResult results) { + IPublishingAction[] actions = createActions(); + for (int i = 0; i < actions.length; i++) + actions[i].perform(info, results); + return Status.OK_STATUS; + } + + protected IPublishingAction[] createActions() { + createAdvice(); + ArrayList result = new ArrayList(); + // create an action that just publishes the raw bundles and features + IPublishingAction action = new MergeResultsAction(new IPublishingAction[] {createFeaturesAction(), createBundlesAction()}, IPublisherResult.MERGE_ALL_NON_ROOT); + result.add(action); + result.add(createSiteXMLAction()); + return (IPublishingAction[]) result.toArray(new IPublishingAction[result.size()]); + } + + private IPublishingAction createSiteXMLAction() { + if (updateSite != null) + return new SiteXMLAction(updateSite); + if (source != null) { + try { + return new SiteXMLAction(new File(source, "site.xml").toURL()); //$NON-NLS-1$ + } catch (MalformedURLException e) { + // never happens + return null; + } + } + return null; + } + + private void createAdvice() { + } + + protected IPublishingAction createFeaturesAction() { + return new FeaturesAction(new File[] {new File(source, "features")}); //$NON-NLS-1$ + } + + protected IPublishingAction createBundlesAction() { + return new BundlesAction(new File[] {new File(source, "plugins")}); //$NON-NLS-1$ + } + +} diff --git a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/RootFilesAction.java b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/RootFilesAction.java index 787ad415b..cd66562c9 100644 --- a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/RootFilesAction.java +++ b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/RootFilesAction.java @@ -86,7 +86,7 @@ public class RootFilesAction extends AbstractPublishingAction { result.addIU(unit, IPublisherResult.ROOT); //Create the artifact descriptor. we have several files so no path on disk - IArtifactDescriptor descriptor = MetadataGeneratorHelper.createArtifactDescriptor(key, null, false, true); + IArtifactDescriptor descriptor = MetadataGeneratorHelper.createArtifactDescriptor(key, null); IRootFilesAdvice advice = getAdvice(configSpec, info); publishArtifact(descriptor, filterRootFiles(advice, info), advice.getRoot(), info, INCLUDE_ROOT); } diff --git a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/UpdateSiteAction.java b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/SiteXMLAction.java index 5c452e5d0..8134daa3b 100644 --- a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/UpdateSiteAction.java +++ b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/actions/SiteXMLAction.java @@ -9,30 +9,38 @@ ******************************************************************************/ package org.eclipse.equinox.internal.p2.publisher.actions; -import java.io.*; 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.core.runtime.*; import org.eclipse.equinox.internal.p2.publisher.*; -import org.eclipse.equinox.internal.p2.publisher.Messages; import org.eclipse.equinox.internal.p2.publisher.features.*; +import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException; import org.eclipse.equinox.internal.provisional.p2.core.repository.IRepository; import org.eclipse.equinox.internal.provisional.p2.metadata.IInstallableUnit; -import org.eclipse.osgi.util.NLS; import org.osgi.framework.Version; -public class UpdateSiteAction extends AbstractPublishingAction { +/** + * Action which processes a site.xml and generates categories. The categorization process + * relies on IUs for the various features to have already been generated. + */ +public class SiteXMLAction extends AbstractPublishingAction { - private URL location; - private IPublisherInfo info; + private UpdateSite updateSite; private SiteCategory defaultCategory; private HashSet defaultCategorySet; - public UpdateSiteAction(URL location, IPublisherInfo info) { - this.location = location; - this.info = info; + public SiteXMLAction(URL location) { + try { + updateSite = UpdateSite.load(location, new NullProgressMonitor()); + } catch (ProvisionException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + initialize(); + } + + public SiteXMLAction(UpdateSite updateSite) { + this.updateSite = updateSite; initialize(); } @@ -46,13 +54,13 @@ public class UpdateSiteAction extends AbstractPublishingAction { } public IStatus perform(IPublisherInfo info, IPublisherResult results) { - generateCategories(results); + generateCategories(info, results); return Status.OK_STATUS; } - private void generateCategories(IPublisherResult results) { + private void generateCategories(IPublisherInfo info, IPublisherResult results) { Map categoriesToFeatureIUs = new HashMap(); - Map featuresToCategories = getFeatureToCategoryMappings(location); + Map featuresToCategories = getFeatureToCategoryMappings(info); for (Iterator i = featuresToCategories.keySet().iterator(); i.hasNext();) { SiteFeature feature = (SiteFeature) i.next(); IInstallableUnit iu = getFeatureIU(feature, results); @@ -76,6 +84,7 @@ public class UpdateSiteAction extends AbstractPublishingAction { private IInstallableUnit getFeatureIU(SiteFeature feature, IPublisherResult results) { String id = MetadataGeneratorHelper.getTransformedId(feature.getFeatureIdentifier(), false, true); Version version = new Version(feature.getFeatureIdentifier()); + // TODO look elsewhere as well. Perhaps in the metadata repos and some advice. Collection ius = results.getIUs(id, null); for (Iterator i = ius.iterator(); i.hasNext();) { IInstallableUnit iu = (IInstallableUnit) i.next(); @@ -90,20 +99,11 @@ public class UpdateSiteAction extends AbstractPublishingAction { * if available. Returns an empty map if there is not site.xml, or no categories. * @return A map of SiteFeature -> Set<SiteCategory>. */ - protected Map getFeatureToCategoryMappings(URL siteLocation) { + protected Map getFeatureToCategoryMappings(IPublisherInfo info) { HashMap mappings = new HashMap(); - if (siteLocation == null) + if (updateSite == null) return mappings; - InputStream input; - SiteModel site = null; - try { - input = new BufferedInputStream(siteLocation.openStream()); - site = new DefaultSiteParser().parse(input); - } catch (FileNotFoundException e) { - //don't complain if the update site is not present - } catch (Exception e) { - LogHelper.log(new Status(IStatus.ERROR, Activator.ID, NLS.bind(Messages.exception_errorParsingUpdateSite, siteLocation), e)); - } + SiteModel site = updateSite.getSite(); if (site == null) return mappings; diff --git a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/features/Messages.java b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/features/Messages.java index 104cc03bf..2a1aced0f 100644 --- a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/features/Messages.java +++ b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/features/Messages.java @@ -33,6 +33,10 @@ public final class Messages extends NLS { public static String DefaultSiteParser_InvalidXMLStream; public static String DefaultSiteParser_mirrors; + public static String ErrorReadingDigest; + public static String ErrorReadingFeature; + public static String ErrorReadingSite; + public static String InvalidRepositoryLocation; static { NLS.initializeMessages(BUNDLE_NAME, Messages.class); } diff --git a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/features/UpdateSite.java b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/features/UpdateSite.java new file mode 100644 index 000000000..362aa2eb3 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/features/UpdateSite.java @@ -0,0 +1,374 @@ +/******************************************************************************* + * 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)); + } + } + } +} diff --git a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/features/messages.properties b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/features/messages.properties index 052ad26c2..7ddef915a 100644 --- a/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/features/messages.properties +++ b/bundles/org.eclipse.equinox.p2.publisher/src/org/eclipse/equinox/internal/p2/publisher/features/messages.properties @@ -28,3 +28,7 @@ DefaultSiteParser_UnknownState= Unknown State \"{0}\". DefaultSiteParser_InvalidXMLStream= The XML stream is not a valid default \"site.xml\" file. The root tag is not site. DefaultSiteParser_mirrors = Error processing update site mirror. +ErrorReadingDigest=Error reading site digest {0}. +ErrorReadingFeature=Error reading feature {0}. +ErrorReadingSite=Error reading update site {0}. +InvalidRepositoryLocation=Invalid repository location {0}. |