/******************************************************************************* * Copyright (c) 2006-2013, Cloudsmith Inc. * The code, documentation and other materials contained herein have been * licensed under the Eclipse Public License - v 1.0 by the copyright holder * listed above, as the Initial Contributor under such license. The text of * such license is available at www.eclipse.org. ******************************************************************************/ package org.eclipse.buckminster.pde.tasks; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Collections; import java.util.Map; import java.util.jar.Attributes; import java.util.jar.Manifest; import org.eclipse.buckminster.core.actor.AbstractActor; import org.eclipse.buckminster.core.actor.IActionContext; import org.eclipse.buckminster.core.cspec.model.CSpec; import org.eclipse.buckminster.core.cspec.model.ComponentIdentifier; import org.eclipse.buckminster.core.cspec.model.ComponentRequest; import org.eclipse.buckminster.core.ctype.IComponentType; import org.eclipse.buckminster.core.reader.AbstractReaderType; import org.eclipse.buckminster.core.reader.IReaderType; import org.eclipse.buckminster.core.reader.ITeamReaderType; import org.eclipse.buckminster.pde.IPDEConstants; import org.eclipse.buckminster.pde.MatchRule; import org.eclipse.buckminster.pde.Messages; import org.eclipse.buckminster.pde.cspecgen.CSpecGenerator; import org.eclipse.buckminster.runtime.BuckminsterException; import org.eclipse.buckminster.runtime.IOUtils; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.equinox.p2.metadata.IVersionedId; import org.eclipse.equinox.p2.metadata.Version; import org.eclipse.equinox.p2.metadata.VersionRange; import org.eclipse.equinox.p2.metadata.VersionedId; import org.eclipse.osgi.util.ManifestElement; import org.eclipse.osgi.util.NLS; import org.osgi.framework.BundleException; import org.osgi.framework.Constants; /** * @author Thomas Hallgren * */ public class BundleConsolidator extends VersionConsolidator { public static IVersionedId getVersionedId(Manifest manifest) throws BundleException { Attributes a = manifest.getMainAttributes(); String symbolicName = a.getValue(Constants.BUNDLE_SYMBOLICNAME); if (symbolicName == null) return null; ManifestElement[] elements = ManifestElement.parseHeader(Constants.BUNDLE_SYMBOLICNAME, symbolicName); String id = elements[0].getValue(); Version version = null; String versionStr = a.getValue(Constants.BUNDLE_VERSION); if (versionStr != null) { try { version = Version.parseVersion(versionStr); } catch (IllegalArgumentException e) { } } return new VersionedId(id, version); } private final byte[] bytes; public BundleConsolidator(File inputFile, File outputFile, File propertiesFile, String qualifier) throws CoreException { super(outputFile, propertiesFile, qualifier); ByteArrayOutputStream output = new ByteArrayOutputStream(); InputStream input = null; try { input = new FileInputStream(inputFile); IOUtils.copy(input, output, null); } catch (IOException e) { throw BuckminsterException.fromMessage(NLS.bind(Messages.unable_to_manifest_from_0, inputFile)); } finally { IOUtils.close(input); } bytes = output.toByteArray(); } public void run() throws CoreException, IOException { IActionContext ctx = AbstractActor.getActiveContext(); Manifest manifest = new Manifest(new ByteArrayInputStream(bytes)); Attributes a = manifest.getMainAttributes(); String symbolicName = a.getValue(Constants.BUNDLE_SYMBOLICNAME); String id = null; Version newVersion = null; boolean changed = false; if (symbolicName != null) { try { ManifestElement[] elements = ManifestElement.parseHeader(Constants.BUNDLE_SYMBOLICNAME, symbolicName); id = elements[0].getValue(); } catch (BundleException be) { throw new IOException(be.getMessage(), be); } String versionStr = a.getValue(Constants.BUNDLE_VERSION); if (versionStr != null) { try { Version version = Version.parseVersion(versionStr); ComponentIdentifier ci = new ComponentIdentifier(id, IComponentType.OSGI_BUNDLE, version); newVersion = replaceQualifier(ci, Collections.<ComponentIdentifier> emptyList()); if (!(newVersion == null || version.equals(newVersion))) { a.put(new Attributes.Name(Constants.BUNDLE_VERSION), newVersion.toString()); changed = true; } } catch (IllegalArgumentException e) { } } } changed = fixRequiredBundleVersions(ctx, a) || changed; changed = addSourceReference(ctx, a) || changed; // mind the order of the operands changed = treatManifest(manifest, id, newVersion) || changed; OutputStream out = null; try { out = new FileOutputStream(getOutputFile()); if (changed) { out = new BufferedOutputStream(out); manifest.write(out); } else out.write(bytes); } finally { IOUtils.close(out); } } protected boolean addSourceReference(IActionContext ctx, Attributes a) throws CoreException, IOException { if (!getBooleanProperty("generateSourceReferences", false)) //$NON-NLS-1$ return false; IContainer container = ResourcesPlugin.getWorkspace().getRoot().getContainerForLocation(ctx.getComponentLocation()); if (container == null) return false; IReaderType rd = AbstractReaderType.getTypeForResource(container); if (!(rd instanceof ITeamReaderType)) return false; String sourceRef = ((ITeamReaderType) rd).getSourceReference(container, new NullProgressMonitor()); if (sourceRef == null) return false; a.putValue("Eclipse-SourceReferences", sourceRef); //$NON-NLS-1$ return true; } @SuppressWarnings("deprecation") protected boolean fixRequiredBundleVersions(IActionContext ctx, Attributes a) throws IOException { String requiredBundles = a.getValue(Constants.REQUIRE_BUNDLE); if (requiredBundles == null) return false; if (!getBooleanProperty(IPDEConstants.PROP_PDE_BUNDLE_RANGE_GENERATION, IPDEConstants.PDE_BUNDLE_RANGE_GENERATION_DEFAULT)) return false; Map<String, ? extends Object> props = getProperties(); MatchRule matchRule = MatchRule.COMPATIBLE; String tmp = (String) props.get(IPDEConstants.PROP_PDE_MATCH_RULE_BUNDLE); if (tmp == null) tmp = (String) props.get(IPDEConstants.PROP_PDE_MATCH_RULE_DEFAULT); if (tmp != null) matchRule = MatchRule.getMatchRule(tmp); if (matchRule == MatchRule.NONE) return false; MatchRule matchRuleLower = MatchRule.EQUIVALENT; tmp = (String) props.get(IPDEConstants.PROP_PDE_MATCH_RULE_BUNDLE_LOWER); if (tmp == null) { tmp = (String) props.get(IPDEConstants.PROP_PDE_MATCH_RULE_DEFAULT_LOWER); if (tmp == null) { // Backward compatibility tmp = (String) props.get(IPDEConstants.PROP_PDE_MATCH_RULE_RETAIN_LOWER); if ("true".equalsIgnoreCase(tmp)) //$NON-NLS-1$ tmp = "perfect"; //$NON-NLS-1$ } } if (tmp != null) matchRuleLower = MatchRule.getMatchRule(tmp); boolean changed = false; StringBuilder bld = new StringBuilder(); try { boolean firstElement = true; for (ManifestElement element : ManifestElement.parseHeader(Constants.REQUIRE_BUNDLE, requiredBundles)) { if (firstElement) firstElement = false; else bld.append(','); bld.append(element); if (element.getAttribute(Constants.BUNDLE_VERSION_ATTRIBUTE) != null) continue; Version v = null; try { CSpec cspec = ctx.getGlobalContext().findCSpec(ctx.getCSpec(), new ComponentRequest(element.getValue(), IComponentType.OSGI_BUNDLE, null)); v = cspec.getVersion(); } catch (CoreException e) { continue; } if (v == null || v.equals(Version.emptyVersion)) continue; VersionRange range = CSpecGenerator.createRuleBasedRange(matchRule, matchRuleLower, v); changed = true; bld.append(';'); bld.append(Constants.BUNDLE_VERSION_ATTRIBUTE); bld.append('='); bld.append('"'); bld.append(range); bld.append('"'); } } catch (BundleException be) { throw new IOException(be.getMessage(), be); } if (changed) a.putValue(Constants.REQUIRE_BUNDLE, bld.toString()); return changed; } /** * Subclasses may override and treat the manifest in whichever way they * like, as long as it stays valid. * * @param manifest * The manifest to treat. Never null. * @param symbolicName * The symbolicName of the bundle, aka bundle ID. May be null. * @param version * The (new) version of the bundle. May be null. * @return Whether the method has changed the manifest or not. */ protected boolean treatManifest(Manifest manifest, String symbolicName, Version version) throws IOException { // empty default implementation return false; } }