package org.eclipse.buckminster.core.materializer; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; import org.eclipse.buckminster.core.CorePlugin; import org.eclipse.buckminster.core.ITargetPlatform; import org.eclipse.buckminster.core.Messages; import org.eclipse.buckminster.core.TargetPlatform; import org.eclipse.buckminster.core.common.model.ExpandingProperties; import org.eclipse.buckminster.core.cspec.IComponentIdentifier; import org.eclipse.buckminster.core.ctype.IComponentType; import org.eclipse.buckminster.core.helpers.FileUtils; import org.eclipse.buckminster.core.metadata.model.Materialization; import org.eclipse.buckminster.core.metadata.model.Resolution; import org.eclipse.buckminster.core.mspec.IMaterializationNode; import org.eclipse.buckminster.core.mspec.IMaterializationSpec; import org.eclipse.buckminster.core.reader.IComponentReader; import org.eclipse.buckminster.core.reader.IReaderType; import org.eclipse.buckminster.core.reader.P2ReaderType; import org.eclipse.buckminster.runtime.BuckminsterException; import org.eclipse.buckminster.runtime.IOUtils; import org.eclipse.buckminster.runtime.URLUtils; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.equinox.internal.p2.artifact.repository.simple.SimpleArtifactDescriptor; import org.eclipse.equinox.internal.p2.engine.InstallableUnitOperand; import org.eclipse.equinox.internal.p2.engine.Phase; import org.eclipse.equinox.internal.p2.engine.PhaseSet; import org.eclipse.equinox.internal.p2.engine.ProvisioningPlan; import org.eclipse.equinox.internal.p2.engine.phases.Collect; import org.eclipse.equinox.internal.p2.metadata.ArtifactKey; import org.eclipse.equinox.p2.core.IProvisioningAgent; import org.eclipse.equinox.p2.core.ProvisionException; import org.eclipse.equinox.p2.engine.IEngine; import org.eclipse.equinox.p2.engine.IProfile; import org.eclipse.equinox.p2.engine.IProfileRegistry; import org.eclipse.equinox.p2.engine.IProvisioningPlan; import org.eclipse.equinox.p2.engine.ProvisioningContext; import org.eclipse.equinox.p2.metadata.IArtifactKey; import org.eclipse.equinox.p2.metadata.IInstallableUnit; import org.eclipse.equinox.p2.metadata.Version; import org.eclipse.equinox.p2.metadata.VersionRange; import org.eclipse.equinox.p2.query.IQueryResult; import org.eclipse.equinox.p2.query.QueryUtil; import org.eclipse.equinox.p2.repository.artifact.IArtifactRepository; import org.eclipse.equinox.p2.repository.artifact.IArtifactRepositoryManager; import org.eclipse.equinox.p2.repository.metadata.IMetadataRepository; import org.eclipse.equinox.p2.repository.metadata.IMetadataRepositoryManager; import org.eclipse.osgi.util.NLS; import org.osgi.framework.Constants; @SuppressWarnings("restriction") public class P2Materializer extends AbstractMaterializer { private static final String CLASSIFIER_OSGI_BUNDLE = "osgi.bundle"; //$NON-NLS-1$ private static final String CLASSIFIER_ORG_ECLIPSE_UPDATE_FEATURE = "org.eclipse.update.feature"; //$NON-NLS-1$ private static final String PROP_ARTIFACT_FOLDER = "artifact.folder"; //$NON-NLS-1$ private static final String PROP_BUNDLE_POOL = "org.eclipse.buckminster.core.bundle.pool"; //$NON-NLS-1$ public static URI cleanURIFromImportType(URI repoLocation) { Map<String, String> props = URLUtils.queryAsParameters(repoLocation.getQuery()); if (props.remove("importType") != null) //$NON-NLS-1$ try { repoLocation = new URI(repoLocation.getScheme(), repoLocation.getAuthority(), repoLocation.getPath(), URLUtils.encodeFromQueryPairs(props), repoLocation.getFragment()); } catch (URISyntaxException e) { throw new IllegalArgumentException(e); } return repoLocation; } static IArtifactRepository getArtifactRepository(IArtifactRepositoryManager manager, URI repoLocation, IProgressMonitor monitor) throws CoreException { SubMonitor subMon = SubMonitor.convert(monitor, 200); try { return manager.loadRepository(repoLocation, subMon.newChild(100)); } catch (ProvisionException e) { return manager.refreshRepository(repoLocation, subMon.newChild(100)); } } static IMetadataRepository getMetadataRepository(IMetadataRepositoryManager manager, URI repoLocation, IProgressMonitor monitor) throws CoreException { SubMonitor subMon = SubMonitor.convert(monitor, 200); try { return manager.loadRepository(repoLocation, subMon.newChild(100)); } catch (ProvisionException e) { return manager.refreshRepository(repoLocation, subMon.newChild(100)); } } @Override public boolean canWorkInParallel() { // Since we start and stop services // return false; } @Override public String getMaterializerRootDir() throws CoreException { ITargetPlatform tp = TargetPlatform.getInstance(); File location = tp.getLocation(); if (location == null) { // Create a default target platform under the buckminster folder // location = tp.getDefaultPlatformLocation(true); // bug 285449: throw exception if we cannot determine the target // location if (location == null) throw BuckminsterException.fromMessage(Messages.Unable_to_determine_platform_install_location); } return location.getAbsolutePath(); } protected File getRuntimeRepository() { // First consult user-supplied override property for a p2 bundle pool. Fail later if it doesn't exist. String bundlePool = System.getProperty(PROP_BUNDLE_POOL); if (bundlePool != null) return new File(bundlePool); // Then try the "osgi.syspath" property, which points to a "plugins" folder. Use the parent folder if it exists. String sysPath = System.getProperty("osgi.syspath"); //$NON-NLS-1$ if (sysPath != null) { File folder = new File(sysPath).getParentFile(); if (folder.isDirectory()) return folder; } // Fall back to the former behaviour of using the platform's install location. return FileUtils.getFile(Platform.getInstallLocation().getURL()); } @Override public List<Materialization> materialize(List<Resolution> resolutions, MaterializationContext context, IProgressMonitor monitor) throws CoreException { Map<File, List<Resolution>> resPerLocation = new HashMap<File, List<Resolution>>(); IMaterializationSpec mspec = context.getMaterializationSpec(); IPath installRoot = mspec.getInstallLocation(); if (installRoot == null) installRoot = Path.fromOSString(getMaterializerRootDir()); else installRoot = Path.fromOSString(ExpandingProperties.expand(context, installRoot.toOSString(), 0)); for (Resolution res : resolutions) { IMaterializationNode node = mspec.getMatchingNode(res); IPath installLocation = null; Map<String, ? extends Object> props = context.getProperties(res); if (node != null) { installLocation = node.getInstallLocation(); if (installLocation != null) { installLocation = Path.fromOSString(ExpandingProperties.expand(props, installLocation.toOSString(), 0)); if (!installLocation.isAbsolute()) installLocation = installRoot.append(installLocation); } } if (installLocation == null) installLocation = installRoot; File locationKey = installLocation.toFile(); List<Resolution> rss = resPerLocation.get(locationKey); if (rss == null) { rss = new ArrayList<Resolution>(); resPerLocation.put(locationKey, rss); } rss.add(res); } SubMonitor subMon = SubMonitor.convert(monitor, 100 + resPerLocation.size() * 1000); IProvisioningAgent p2Agent = CorePlugin.getDefault().getResolverAgent(); IMetadataRepositoryManager mdrManager = (IMetadataRepositoryManager) p2Agent.getService(IMetadataRepositoryManager.SERVICE_NAME); IArtifactRepositoryManager arManager = (IArtifactRepositoryManager) p2Agent.getService(IArtifactRepositoryManager.SERVICE_NAME); IEngine engine = (IEngine) p2Agent.getService(IEngine.SERVICE_NAME); IProfileRegistry registry = (IProfileRegistry) p2Agent.getService(IProfileRegistry.SERVICE_NAME); Map<URI, IMetadataRepository> knownMDRs = new HashMap<URI, IMetadataRepository>(); Map<URI, IArtifactRepository> knownARs = new HashMap<URI, IArtifactRepository>(); try { File file = getRuntimeRepository(); URI runtimeAR = file.toURI(); knownARs.put(runtimeAR, arManager.loadRepository(runtimeAR, subMon.newChild(100))); } catch (Exception e) { // Ignore CorePlugin.getLogger().warning(e, "Unable to load runtime repository: " + e.getMessage()); //$NON-NLS-1$ } for (Map.Entry<File, List<Resolution>> entry : resPerLocation.entrySet()) { // ensure the user-specified artifact repos will be consulted by // loading them final File destDir = entry.getKey(); // do a create here to ensure that we don't default to a #load later // and grab a repo which is the wrong // type // e.g. extension location type because a plugins/ directory exists. IArtifactRepository destAR; try { destAR = arManager.createRepository(destDir.toURI(), "Runnable repository.", //$NON-NLS-1$ IArtifactRepositoryManager.TYPE_SIMPLE_REPOSITORY, null); } catch (ProvisionException e) { // ignore... perhaps one already exists and we will just load it // later destAR = arManager.loadRepository(destDir.toURI(), null); } List<Resolution> ress = entry.getValue(); List<IInstallableUnit> ius = new ArrayList<IInstallableUnit>(ress.size()); for (Resolution res : ress) { SubMonitor subSubMon = subMon.newChild(800 / ress.size()); subSubMon.setWorkRemaining(1000); IComponentIdentifier cid = res.getComponentIdentifier(); Version version = Version.create(cid.getVersion().toString()); URI repoURI = cleanURIFromImportType(URI.create(res.getRepository())); String path = repoURI.getPath(); if (path.endsWith(".jar")) //$NON-NLS-1$ { // This is a direct pointer to an artifact, not a repository // fetchP2object(context, destDir, destAR, res, subSubMon, cid, version); continue; } // Try URI as a P2 repository IMetadataRepository mdr = knownMDRs.get(repoURI); if (mdr == null) { try { mdr = getMetadataRepository(mdrManager, repoURI, subSubMon.newChild(500)); knownMDRs.put(repoURI, mdr); } catch (ProvisionException pe) { if (ProvisionException.REPOSITORY_NOT_FOUND != pe.getStatus().getCode()) throw pe; // URI is not a p2 repository fetchP2object(context, destDir, destAR, res, subSubMon, cid, version); continue; } } VersionRange range = new VersionRange(version, true, version, true); String name = cid.getName(); boolean isFeature = IComponentType.ECLIPSE_FEATURE.equals(cid.getComponentTypeID()); if (isFeature) // Since this is what we want in the target platform name = name + ".feature.jar"; //$NON-NLS-1$ IQueryResult<IInstallableUnit> result = mdr.query(QueryUtil.createIUQuery(name, range), subSubMon.newChild(250)); Iterator<IInstallableUnit> itor = result.iterator(); if (!itor.hasNext()) throw new ProvisionException(NLS.bind(Messages.Unable_to_resolve_0_1_in_MDR_2, new Object[] { cid.getName(), version, res.getRepository() })); IInstallableUnit iu = itor.next(); ius.add(iu); // Check if this IU has artifacts and if so, load the artifact // repository // if (iu.getArtifacts().size() > 0) { IArtifactRepository ar = knownARs.get(repoURI); if (ar == null) { ar = getArtifactRepository(arManager, repoURI, subSubMon.newChild(250)); knownARs.put(repoURI, ar); } } else subSubMon.worked(250); } IArtifactRepository tempAr = P2ReaderType.getTempAR(subMon.newChild(1)); knownARs.put(tempAr.getLocation(), tempAr); // create the operands from the list of IUs InstallableUnitOperand[] operands = new InstallableUnitOperand[ius.size()]; int i = 0; for (IInstallableUnit iu : ius) operands[i++] = new InstallableUnitOperand(null, iu); // call the engine with only the "collect" phase so all we do is // download String destDirStr = destDir.toString(); if (!registry.containsProfile(destDirStr)) { Map<String, String> properties = new HashMap<String, String>(); properties.put(IProfile.PROP_SHARED_CACHE, Boolean.toString(false)); properties.put(IProfile.PROP_INSTALL_FEATURES, Boolean.toString(true)); properties.put(IProfile.PROP_CACHE, destDirStr); properties.put(IProfile.PROP_INSTALL_FOLDER, destDirStr); registry.addProfile(destDirStr, properties); } IProfile profile = registry.getProfile(destDirStr); try { PhaseSet phaseSet = new PhaseSet(new Phase[] { new Collect(100) }) { /* * nothing * to * override */ }; Set<URI> mdrURIs = knownMDRs.keySet(); Set<URI> arURIs = knownARs.keySet(); ProvisioningContext pctx = new ProvisioningContext(p2Agent); pctx.setMetadataRepositories(mdrURIs.toArray(new URI[mdrURIs.size()])); pctx.setArtifactRepositories(arURIs.toArray(new URI[arURIs.size()])); IProvisioningPlan plan = new ProvisioningPlan(profile, operands, pctx); IStatus status = engine.perform(plan, phaseSet, subMon.newChild(200)); if (status.getSeverity() == IStatus.ERROR) throw BuckminsterException.wrap(status); } finally { if (profile != null) registry.removeProfile(profile.getProfileId()); } // The resource holding the target archive must be refreshed (if // indeed, it is a resource at all) IContainer[] destConts = ResourcesPlugin.getWorkspace().getRoot().findContainersForLocationURI(destDir.toURI()); if (destConts != null && destConts.length > 0) { for (IContainer destCont : destConts) { IProject project = destCont.getProject(); if (project.isOpen()) project.refreshLocal(IResource.DEPTH_INFINITE, subMon.newChild(1)); } } } TargetPlatform.getInstance().locationsChanged(resPerLocation.keySet()); return Collections.emptyList(); } private void convertSourceJar(IComponentIdentifier cid, File bundleJar, Manifest mf) throws IOException { File tempRoot = bundleJar.getParentFile(); File outFile = null; ZipOutputStream out = null; InputStream in = new FileInputStream(bundleJar); try { String name = cid.getName(); String binName = name.substring(0, name.length() - 7); String strVer = cid.getVersion().toString(); if (mf == null) mf = new Manifest(); Attributes attrs = mf.getMainAttributes(); attrs.putValue(Constants.BUNDLE_SYMBOLICNAME, name); attrs.putValue(Constants.BUNDLE_NAME, "Source for " + binName); //$NON-NLS-1$ attrs.putValue(Constants.BUNDLE_VERSION, strVer); attrs.putValue(Constants.BUNDLE_MANIFESTVERSION, "2"); //$NON-NLS-1$ attrs.putValue("Eclipse-SourceBundle", binName + ";version=" + strVer); //$NON-NLS-1$//$NON-NLS-2$ outFile = File.createTempFile("newbundle-", ".jar", tempRoot); //$NON-NLS-1$//$NON-NLS-2$ out = new ZipOutputStream(new FileOutputStream(outFile)); ZipEntry ze = new ZipEntry("META-INF/"); //$NON-NLS-1$ out.putNextEntry(ze); ze = new ZipEntry("META-INF/MANIFEST.MF"); //$NON-NLS-1$ out.putNextEntry(ze); mf.write(out); ZipInputStream zin = new ZipInputStream(in); while ((ze = zin.getNextEntry()) != null) { if ("META-INF/".equals(ze.getName()) || "META-INF/MANIFEST.MF".equals(ze.getName())) //$NON-NLS-1$//$NON-NLS-2$ continue; out.putNextEntry(ze); if (!ze.isDirectory()) IOUtils.copy(zin, out, null); } } finally { IOUtils.close(in); IOUtils.close(out); } File tmpRename = File.createTempFile("oldbundle-", ".jar", tempRoot); //$NON-NLS-1$//$NON-NLS-2$; if (tmpRename.delete() && bundleJar.renameTo(tmpRename)) { if (outFile.renameTo(bundleJar)) tmpRename.delete(); else { // Attempt to roll back tmpRename.renameTo(bundleJar); throw new IOException("Unable to rename " + outFile.getAbsolutePath() + " to " + bundleJar.getAbsolutePath()); //$NON-NLS-1$ //$NON-NLS-2$ } } else throw new IOException("Unable to rename " + bundleJar.getAbsolutePath() + " to " + tmpRename.getAbsolutePath()); //$NON-NLS-1$//$NON-NLS-2$ } private void fetchP2object(MaterializationContext context, File destDir, IArtifactRepository destAR, Resolution res, SubMonitor subSubMon, IComponentIdentifier cid, Version version) throws CoreException { IArtifactKey aKey; if (IComponentType.ECLIPSE_FEATURE.equals(cid.getComponentTypeID())) aKey = new ArtifactKey(CLASSIFIER_ORG_ECLIPSE_UPDATE_FEATURE, cid.getName(), version); else aKey = new ArtifactKey(CLASSIFIER_OSGI_BUNDLE, cid.getName(), version); if (destAR.contains(aKey)) return; IComponentType ctype = CorePlugin.getDefault().getComponentType(cid.getComponentTypeID()); IPath location = Path.fromOSString(destDir.getAbsolutePath()); IPath ctypeRelative = ctype.getRelativeLocation(); if (ctypeRelative != null) location = location.append(ctypeRelative); location.toFile().mkdirs(); String leafName = cid.getName() + '_' + cid.getVersion(); if (res.isUnpack()) { location = location.append(leafName); location = location.addTrailingSeparator(); } else location = location.append(leafName + ".jar"); //$NON-NLS-1$ IReaderType readerType = CorePlugin.getDefault().getReaderType(res.getReaderTypeId()); IComponentReader reader = readerType.getReader(res, context, subSubMon.newChild(10)); try { reader.materialize(location, res, context, subSubMon.newChild(500)); } finally { try { reader.close(); } catch (IOException e) { throw BuckminsterException.wrap(e); } } if (cid.getName().endsWith(".source") && IComponentType.OSGI_BUNDLE.equals(cid.getComponentTypeID()) && !res.isUnpack()) { //$NON-NLS-1$ // It is not unlikely that we have downloaded a source bundle from a // maven repository at this point. They often lack the some settings // required by Eclipse in their manifest. If that's the case, then // we can fix it here so that source lookups will work in the IDE. Object convertSource = context.get("buckminster.convert.source"); //$NON-NLS-1$ if (convertSource != null && "true".equalsIgnoreCase(convertSource.toString())) { //$NON-NLS-1$ try { File bundleJar = location.toFile(); Manifest mf = null; JarFile jar = null; try { jar = new JarFile(bundleJar); mf = jar.getManifest(); } finally { if (jar != null) { try { jar.close(); } catch (IOException e) { } } } if (mf == null || mf.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME) == null) { convertSourceJar(cid, bundleJar, mf); } } catch (Exception e) { // Not fatal but a warning is appropriate CorePlugin.getLogger().warning(e.getMessage(), e); } } } SimpleArtifactDescriptor desc = new SimpleArtifactDescriptor(aKey); if (IComponentType.ECLIPSE_FEATURE.equals(cid.getComponentTypeID()) || res.isUnpack()) desc.addRepositoryProperties(Collections.singletonMap(PROP_ARTIFACT_FOLDER, Boolean.toString(true))); destAR.addDescriptor(desc, subSubMon.newChild(10)); } }