| /******************************************************************************* |
| * 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.*; |
| import java.util.*; |
| 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; |
| import org.eclipse.equinox.internal.provisional.p2.artifact.repository.IArtifactDescriptor; |
| import org.eclipse.equinox.internal.provisional.p2.metadata.*; |
| import org.eclipse.equinox.internal.provisional.p2.metadata.MetadataFactory.InstallableUnitDescription; |
| import org.eclipse.osgi.service.resolver.VersionRange; |
| import org.osgi.framework.Version; |
| |
| /** |
| * Publish IUs for all of the features in the given set of locations. The locations can |
| * 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$ |
| |
| 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 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) { |
| generateFeatureIUs(featureList, results, info); |
| return Status.OK_STATUS; |
| } |
| |
| private File[] expandLocations(File[] list) { |
| 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()) { |
| // 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); |
| } |
| } |
| } |
| |
| 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]; |
| // 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); |
| } |
| } |
| |
| 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; |
| } |
| |
| 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); |
| } |
| } |
| |
| 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; |
| } |
| |
| 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); |
| } |
| |
| private ArrayList generateRootFileIUs(String featureId, String featureVersion, Properties props, File location, IPublisherResult result, IPublisherInfo info) { |
| ArrayList ius = new ArrayList(); |
| FileSetDescriptor[] rootFileDescriptors = getRootFileDescriptors(props); |
| for (int i = 0; i < rootFileDescriptors.length; i++) { |
| IInstallableUnit iu = generateRootFileIU(featureId, featureVersion, location, rootFileDescriptors[i], result, info); |
| ius.add(iu); |
| } |
| return ius; |
| } |
| |
| private Properties loadProperties(File location, String file) { |
| Properties props = new Properties(); |
| File tempFile = null; |
| try { |
| // if the feature is a dir then just return the location |
| if (!location.isDirectory()) { |
| // otherwise extract the JAR into a temp location and return that location |
| tempFile = File.createTempFile("p2.generator", ""); //$NON-NLS-1$ //$NON-NLS-2$ |
| FileUtils.unzipFile(location, tempFile); |
| location = tempFile; |
| } |
| try { |
| InputStream in = null; |
| try { |
| in = new BufferedInputStream(new FileInputStream(new File(location, file))); //$NON-NLS-1$ |
| props.load(in); |
| } finally { |
| if (in != null) |
| in.close(); |
| } |
| } catch (FileNotFoundException e) { |
| // ignore if it is just a file not found. |
| } catch (IOException e) { |
| LogHelper.log(new Status(IStatus.ERROR, Activator.ID, "Error parsing " + file, e)); //$NON-NLS-1$ |
| } |
| } catch (IOException e) { |
| LogHelper.log(new Status(IStatus.ERROR, Activator.ID, "Error publishing artifacts", e)); //$NON-NLS-1$ |
| } finally { |
| if (tempFile != null) |
| tempFile.delete(); |
| } |
| return props; |
| } |
| |
| private IInstallableUnit generateRootFileIU(String featureId, String featureVersion, File location, FileSetDescriptor rootFile, IPublisherResult result, IPublisherInfo info) { |
| Object[] iuAndFiles = MetadataGeneratorHelper.createFeatureRootFileIU(featureId, featureVersion, location, rootFile); |
| IInstallableUnit iuResult = (IInstallableUnit) iuAndFiles[0]; |
| File[] fileResult = (File[]) iuAndFiles[1]; |
| if (fileResult != null && fileResult.length > 0) { |
| IArtifactKey artifact = iuResult.getArtifacts()[0]; |
| ArtifactDescriptor descriptor = new ArtifactDescriptor(artifact); |
| publishArtifact(descriptor, fileResult, info, 0); |
| } |
| result.addIU(iuResult, IPublisherResult.NON_ROOT); |
| return iuResult; |
| } |
| |
| protected FileSetDescriptor[] getRootFileDescriptors(Properties props) { |
| HashMap result = new HashMap(); |
| for (Iterator i = props.keySet().iterator(); i.hasNext();) { |
| String property = (String) i.next(); |
| // we only care about root properties |
| if (!property.startsWith("root.")) //$NON-NLS-1$ |
| continue; |
| String[] spec = getArrayFromString(property, "."); //$NON-NLS-1$ |
| String descriptorKey = spec[0]; |
| String configSpec = null; |
| // if the spec is 4 or more then there must be a config involved to get it |
| if (spec.length > 3) { |
| configSpec = createConfigSpec(spec[2], spec[1], spec[3]); |
| descriptorKey += "." + createIdString(configSpec); //$NON-NLS-1$ |
| } |
| |
| FileSetDescriptor descriptor = (FileSetDescriptor) result.get(descriptorKey); |
| if (descriptor == null) { |
| descriptor = new FileSetDescriptor(descriptorKey, configSpec); |
| result.put(descriptorKey, descriptor); |
| } |
| // if the last segment in the spec is "link" |
| if (spec[spec.length - 1] == "link") //$NON-NLS-1$ |
| descriptor.setLinks(props.getProperty(property)); |
| else { |
| // if the second last segment is "permissions" |
| if (spec[spec.length - 2].equals("permissions")) //$NON-NLS-1$ |
| descriptor.addPermissions(new String[] {spec[spec.length - 1], props.getProperty(property)}); |
| else { |
| // so it is not a link or a permissions, it must be a straight file copy (with or without a config) |
| descriptor.setFiles(props.getProperty(property)); |
| } |
| } |
| } |
| Collection values = result.values(); |
| return (FileSetDescriptor[]) values.toArray(new FileSetDescriptor[values.size()]); |
| } |
| |
| /** |
| * Gather any advice we can from the given feature. In particular, it may have |
| * information about the shape of the bundles it includes. The discovered advice is |
| * added to the given result. |
| * @param feature the feature to process |
| * @param info the publishing info to update |
| */ |
| public void gatherBundleShapeAdvice(Feature feature, IPublisherInfo info) { |
| FeatureEntry entries[] = feature.getEntries(); |
| for (int i = 0; i < entries.length; i++) { |
| FeatureEntry entry = entries[i]; |
| if (entry.isUnpack()) |
| info.addAdvice(new BundleShapeAdvice(entry.getId(), new Version(entry.getVersion()), IBundleShapeAdvice.DIR)); |
| } |
| } |
| |
| 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); |
| iu.setId(id); |
| Version version = new Version(feature.getVersion()); |
| iu.setVersion(version); |
| iu.setProperty(IInstallableUnit.PROP_NAME, feature.getLabel()); |
| if (feature.getLicense() != null) |
| iu.setLicense(new License(feature.getLicenseURL(), feature.getLicense())); |
| if (feature.getCopyright() != null) |
| iu.setCopyright(new Copyright(feature.getCopyrightURL(), feature.getCopyright())); |
| iu.setUpdateDescriptor(MetadataFactory.createUpdateDescriptor(id, new VersionRange(new Version(0, 0, 0), true, new Version(feature.getVersion()), false), IUpdateDescriptor.NORMAL, null)); |
| |
| // generate requirements for the feature inclusions/requirement |
| FeatureEntry entries[] = feature.getEntries(); |
| RequiredCapability[] required = new RequiredCapability[entries.length + 1]; |
| for (int i = 0; i < entries.length; i++) { |
| VersionRange range = getVersionRange(entries[i]); |
| required[i] = MetadataFactory.createRequiredCapability(IInstallableUnit.NAMESPACE_IU_ID, getTransformedId(entries[i].getId(), entries[i].isPlugin(), /*isGroup*/true), range, getFilter(entries[i]), entries[i].isOptional(), false); |
| } |
| required[entries.length] = MetadataFactory.createRequiredCapability(IInstallableUnit.NAMESPACE_IU_ID, featureIU.getId(), new VersionRange(featureIU.getVersion(), true, featureIU.getVersion(), true), INSTALL_FEATURES_FILTER, false, false); |
| iu.setRequiredCapabilities(required); |
| iu.setTouchpointType(TouchpointType.NONE); |
| iu.setProperty(IInstallableUnit.PROP_TYPE_GROUP, Boolean.TRUE.toString()); |
| // TODO: shouldn't the filter for the group be constructed from os, ws, arch, nl |
| // of the feature? |
| // iu.setFilter(filter); |
| iu.setCapabilities(new ProvidedCapability[] {MetadataGeneratorHelper.createSelfCapability(id, version)}); |
| |
| 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 VersionRange getVersionRange(FeatureEntry entry) { |
| String versionSpec = entry.getVersion(); |
| if (versionSpec == null) |
| // TODO should really be returning VersionRange.emptyRange here... |
| return null; |
| Version version = new Version(versionSpec); |
| // if this is an includes then return either a completely open range (if the verison is 0.0.0) |
| // or an exact range |
| if (!entry.isRequires()) { |
| if (version.equals(Version.emptyVersion)) |
| 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... |
| return null; |
| if (match.equals("perfect")) //$NON-NLS-1$ |
| return new VersionRange(version, true, version, true); |
| if (match.equals("equivalent")) { //$NON-NLS-1$ |
| Version upper = new Version(version.getMajor(), version.getMinor() + 1, 0); |
| return new VersionRange(version, true, upper, false); |
| } |
| if (match.equals("compatible")) { //$NON-NLS-1$ |
| Version upper = new Version(version.getMajor() + 1, 0, 0); |
| return new VersionRange(version, true, upper, false); |
| } |
| if (match.equals("greaterOrEqual")) //$NON-NLS-1$ |
| return new VersionRange(version, true, new VersionRange(null).getMaximum(), true); |
| return null; |
| } |
| |
| public String getFilter(FeatureEntry entry) { |
| StringBuffer result = new StringBuffer(); |
| result.append("(&"); //$NON-NLS-1$ |
| if (entry.getFilter() != null) |
| result.append(entry.getFilter()); |
| if (entry.getOS() != null) |
| result.append("(osgi.os=" + entry.getOS() + ')');//$NON-NLS-1$ |
| if (entry.getWS() != null) |
| result.append("(osgi.ws=" + entry.getWS() + ')');//$NON-NLS-1$ |
| if (entry.getArch() != null) |
| result.append("(osgi.arch=" + entry.getArch() + ')');//$NON-NLS-1$ |
| if (entry.getNL() != null) |
| result.append("(osgi.nl=" + entry.getNL() + ')');//$NON-NLS-1$ |
| if (result.length() == 2) |
| return null; |
| result.append(')'); |
| return result.toString(); |
| } |
| |
| protected Feature[] getFeatures(File[] locations) { |
| ArrayList result = new ArrayList(locations.length); |
| for (int i = 0; i < locations.length; i++) { |
| Feature feature = new FeatureParser().parse(locations[i]); |
| if (feature != null) { |
| feature.setLocation(locations[i].getAbsolutePath()); |
| result.add(feature); |
| } |
| } |
| return (Feature[]) result.toArray(new Feature[result.size()]); |
| } |
| } |