/******************************************************************************* * Copyright (c) 2008, 2017 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 * IBM - ongoing development * Rapicorp - ongoing development ******************************************************************************/ package org.eclipse.equinox.p2.publisher.eclipse; import java.io.File; import java.util.*; import java.util.Map.Entry; import org.eclipse.core.runtime.*; import org.eclipse.equinox.frameworkadmin.BundleInfo; import org.eclipse.equinox.internal.p2.metadata.TouchpointInstruction; import org.eclipse.equinox.internal.p2.publisher.eclipse.GeneratorBundleInfo; import org.eclipse.equinox.p2.metadata.*; import org.eclipse.equinox.p2.metadata.MetadataFactory.InstallableUnitDescription; import org.eclipse.equinox.p2.metadata.expression.IMatchExpression; import org.eclipse.equinox.p2.publisher.*; import org.eclipse.equinox.p2.repository.IRepository; import org.eclipse.equinox.p2.repository.IRepositoryReference; import org.eclipse.equinox.spi.p2.publisher.PublisherHelper; import org.eclipse.osgi.util.ManifestElement; import org.osgi.framework.BundleException; import org.osgi.framework.Constants; /** * Publish CUs for all the configuration data in the current result. * This adds config-specific CUs to capture start levels etc found in the config.ini * etc for is os, ws, arch combination seen so far. */ public class ConfigCUsAction extends AbstractPublisherAction { protected static final String DEFAULT_START_LEVEL = "osgi.bundles.defaultStartLevel"; //$NON-NLS-1$ private static Collection PROPERTIES_TO_SKIP; private static HashSet PROGRAM_ARGS_TO_SKIP; protected Version version; protected String id; protected String flavor; IPublisherResult outerResults = null; // TODO consider moving this filtering to the LaunchingAdvice and ConfigAdvice so // it is not hardcoded in the action. static { PROPERTIES_TO_SKIP = new HashSet<>(); PROPERTIES_TO_SKIP.add("osgi.frameworkClassPath"); //$NON-NLS-1$ PROPERTIES_TO_SKIP.add("osgi.framework"); //$NON-NLS-1$ PROPERTIES_TO_SKIP.add("osgi.bundles"); //$NON-NLS-1$ PROPERTIES_TO_SKIP.add("eof"); //$NON-NLS-1$ PROPERTIES_TO_SKIP.add("eclipse.p2.profile"); //$NON-NLS-1$ PROPERTIES_TO_SKIP.add("eclipse.p2.data.area"); //$NON-NLS-1$ PROPERTIES_TO_SKIP.add("org.eclipse.update.reconcile"); //$NON-NLS-1$ PROPERTIES_TO_SKIP.add("org.eclipse.equinox.simpleconfigurator.configUrl"); //$NON-NLS-1$ PROGRAM_ARGS_TO_SKIP = new HashSet<>(); PROGRAM_ARGS_TO_SKIP.add("--launcher.library"); //$NON-NLS-1$ PROGRAM_ARGS_TO_SKIP.add("-startup"); //$NON-NLS-1$ PROGRAM_ARGS_TO_SKIP.add("-configuration"); //$NON-NLS-1$ } public static String getAbstractCUCapabilityNamespace(String id, String type, String flavor, String configSpec) { return flavor + id; } public static String getAbstractCUCapabilityId(String id, String type, String flavor, String configSpec) { return id + "." + type; //$NON-NLS-1$ } /** * Returns the id of the top level IU published by this action for the given id and flavor. * @param id the id of the application being published * @param flavor the flavor being published * @return the if for ius published by this action */ public static String computeIUId(String id, String flavor) { return flavor + id + ".configuration"; //$NON-NLS-1$ } public ConfigCUsAction(IPublisherInfo info, String flavor, String id, Version version) { this.flavor = flavor; this.id = id; this.version = version; } @Override public IStatus perform(IPublisherInfo publisherInfo, IPublisherResult results, IProgressMonitor monitor) { IPublisherResult innerResult = new PublisherResult(); this.outerResults = results; this.info = publisherInfo; // we have N platforms, generate a CU for each // TODO try and find common properties across platforms String[] configSpecs = publisherInfo.getConfigurations(); for (int i = 0; i < configSpecs.length; i++) { if (monitor.isCanceled()) return Status.CANCEL_STATUS; String configSpec = configSpecs[i]; Collection configAdvice = publisherInfo.getAdvice(configSpec, false, id, version, IConfigAdvice.class); BundleInfo[] bundles = fillInBundles(configAdvice, results); publishBundleCUs(publisherInfo, bundles, configSpec, innerResult); publishConfigIUs(configAdvice, innerResult, configSpec); Collection launchingAdvice = publisherInfo.getAdvice(configSpec, false, id, version, IExecutableAdvice.class); publishIniIUs(launchingAdvice, innerResult, configSpec); } // merge the IUs into the final result as non-roots and create a parent IU that captures them all results.merge(innerResult, IPublisherResult.MERGE_ALL_NON_ROOT); publishTopLevelConfigurationIU(innerResult.getIUs(null, IPublisherResult.ROOT), results); return Status.OK_STATUS; } private void publishTopLevelConfigurationIU(Collection children, IPublisherResult result) { InstallableUnitDescription descriptor = createParentIU(children, computeIUId(id, flavor), version); descriptor.setSingleton(true); IInstallableUnit rootIU = MetadataFactory.createInstallableUnit(descriptor); if (rootIU == null) return; result.addIU(rootIU, IPublisherResult.ROOT); } // there seem to be cases where the bundle infos are not filled in with symbolic name and version. // fill in the missing data. private BundleInfo[] fillInBundles(Collection configAdvice, IPublisherResult results) { ArrayList result = new ArrayList<>(); for (IConfigAdvice advice : configAdvice) { int defaultStart = BundleInfo.NO_LEVEL; Map adviceProperties = advice.getProperties(); if (adviceProperties.containsKey(DEFAULT_START_LEVEL)) { try { defaultStart = Integer.parseInt(adviceProperties.get(DEFAULT_START_LEVEL)); } catch (NumberFormatException e) { //don't know default } } BundleInfo[] bundles = advice.getBundles(); for (int i = 0; i < bundles.length; i++) { BundleInfo bundleInfo = bundles[i]; if (bundleInfo.getStartLevel() != BundleInfo.NO_LEVEL && bundleInfo.getStartLevel() == defaultStart) { bundleInfo.setStartLevel(BundleInfo.NO_LEVEL); } // prime the result with the current info. This will be replaced if there is more info... if ((bundleInfo.getSymbolicName() != null && bundleInfo.getVersion() != null) || bundleInfo.getLocation() == null) result.add(bundles[i]); else { try { File location = new File(bundleInfo.getLocation()); Dictionary manifest = BundlesAction.loadManifestIgnoringExceptions(location); if (manifest == null) continue; GeneratorBundleInfo newInfo = new GeneratorBundleInfo(bundleInfo); ManifestElement[] element = ManifestElement.parseHeader("dummy-bsn", manifest.get(Constants.BUNDLE_SYMBOLICNAME)); //$NON-NLS-1$ newInfo.setSymbolicName(element[0].getValue()); newInfo.setVersion(manifest.get(Constants.BUNDLE_VERSION)); result.add(newInfo); } catch (BundleException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } return result.toArray(new BundleInfo[result.size()]); } /** * Publish the IUs that capture the eclipse.ini information such as vmargs and program args, etc */ private void publishIniIUs(Collection launchingAdvice, IPublisherResult results, String configSpec) { if (launchingAdvice.isEmpty()) return; String configureData = ""; //$NON-NLS-1$ String unconfigureData = ""; //$NON-NLS-1$ if (!launchingAdvice.isEmpty()) { String[] dataStrings = getLauncherConfigStrings(launchingAdvice); configureData += dataStrings[0]; unconfigureData += dataStrings[1]; } // if there is nothing to configure or unconfigure, then don't even bother generating this IU if (configureData.length() == 0 && unconfigureData.length() == 0) return; Map touchpointData = new HashMap<>(); touchpointData.put("configure", configureData); //$NON-NLS-1$ touchpointData.put("unconfigure", unconfigureData); //$NON-NLS-1$ IInstallableUnit cu = createCU(id, version, "ini", flavor, configSpec, touchpointData); //$NON-NLS-1$ results.addIU(cu, IPublisherResult.ROOT); } /** * Publish the IUs that capture the config.ini information such as properties etc */ private void publishConfigIUs(Collection configAdvice, IPublisherResult results, String configSpec) { if (configAdvice.isEmpty()) return; String configureData = ""; //$NON-NLS-1$ String unconfigureData = ""; //$NON-NLS-1$ if (!configAdvice.isEmpty()) { String[] dataStrings = getConfigurationStrings(configAdvice); configureData += dataStrings[0]; unconfigureData += dataStrings[1]; } // if there is nothing to configure or unconfigure, then don't even bother generating this IU if (configureData.length() == 0 && unconfigureData.length() == 0) return; Map touchpointData = new HashMap<>(); touchpointData.put("configure", configureData); //$NON-NLS-1$ touchpointData.put("unconfigure", unconfigureData); //$NON-NLS-1$ IInstallableUnit cu = createCU(id, version, "config", flavor, configSpec, touchpointData); //$NON-NLS-1$ results.addIU(cu, IPublisherResult.ROOT); } /** * Create a CU whose id is flavor+id.type.configspec with the given version. * The resultant IU has the self capability and an abstract capabilty in the flavor+id namespace * with the name id.type and the given version. This allows others to create an abstract * dependency on having one of these things around but not having to list out the configs. */ private IInstallableUnit createCU(String cuId, Version cuVersion, String cuType, String cuFlavor, String configSpec, Map touchpointData) { InstallableUnitDescription cu = new InstallableUnitDescription(); String resultId = createCUIdString(cuId, cuType, cuFlavor, configSpec); cu.setId(resultId); cu.setVersion(cuVersion); cu.setFilter(createFilterSpec(configSpec)); IProvidedCapability selfCapability = PublisherHelper.createSelfCapability(resultId, cuVersion); String namespace = getAbstractCUCapabilityNamespace(cuId, cuType, cuFlavor, configSpec); String abstractId = getAbstractCUCapabilityId(cuId, cuType, cuFlavor, configSpec); IProvidedCapability abstractCapability = MetadataFactory.createProvidedCapability(namespace, abstractId, cuVersion); cu.setCapabilities(new IProvidedCapability[] {selfCapability, abstractCapability}); cu.addTouchpointData(MetadataFactory.createTouchpointData(touchpointData)); cu.setTouchpointType(PublisherHelper.TOUCHPOINT_OSGI); return MetadataFactory.createInstallableUnit(cu); } protected String[] getConfigurationStrings(Collection configAdvice) { String configurationData = ""; //$NON-NLS-1$ String unconfigurationData = ""; //$NON-NLS-1$ Set properties = new HashSet<>(); for (IConfigAdvice advice : configAdvice) { for (Entry aProperty : advice.getProperties().entrySet()) { String key = aProperty.getKey(); if (shouldPublishProperty(key) && !properties.contains(key)) { properties.add(key); Map parameters = new LinkedHashMap<>(); parameters.put("propName", key); //$NON-NLS-1$ parameters.put("propValue", aProperty.getValue()); //$NON-NLS-1$ configurationData += TouchpointInstruction.encodeAction("setProgramProperty", parameters); //$NON-NLS-1$ parameters.put("propValue", ""); //$NON-NLS-1$//$NON-NLS-2$ unconfigurationData += TouchpointInstruction.encodeAction("setProgramProperty", parameters); //$NON-NLS-1$ } } if (advice instanceof ProductFileAdvice) { for (IRepositoryReference repo : ((ProductFileAdvice) advice).getUpdateRepositories()) { Map parameters = new LinkedHashMap<>(); parameters.put("type", Integer.toString(repo.getType())); //$NON-NLS-1$ parameters.put("location", repo.getLocation().toString()); //$NON-NLS-1$ parameters.put("enabled", Boolean.toString((repo.getOptions() & IRepository.ENABLED) == IRepository.ENABLED)); //$NON-NLS-1$ configurationData += TouchpointInstruction.encodeAction("addRepository", parameters); //$NON-NLS-1$ parameters.remove("enabled"); //$NON-NLS-1$ unconfigurationData += TouchpointInstruction.encodeAction("removeRepository", parameters);//$NON-NLS-1$ } } } return new String[] {configurationData, unconfigurationData}; } private boolean shouldPublishProperty(String key) { return !PROPERTIES_TO_SKIP.contains(key); } private boolean shouldPublishJvmArg(String key) { return true; } private boolean shouldPublishProgramArg(String key) { return !PROGRAM_ARGS_TO_SKIP.contains(key); } protected String[] getLauncherConfigStrings(Collection launchingAdvice) { String configurationData = ""; //$NON-NLS-1$ String unconfigurationData = ""; //$NON-NLS-1$ Map touchpointParameters = new LinkedHashMap<>(); Set jvmSet = new HashSet<>(); Set programSet = new HashSet<>(); for (IExecutableAdvice advice : launchingAdvice) { String[] jvmArgs = advice.getVMArguments(); for (int i = 0; i < jvmArgs.length; i++) if (shouldPublishJvmArg(jvmArgs[i]) && !jvmSet.contains(jvmArgs[i])) { jvmSet.add(jvmArgs[i]); touchpointParameters.clear(); touchpointParameters.put("jvmArg", jvmArgs[i]); //$NON-NLS-1$ configurationData += TouchpointInstruction.encodeAction("addJvmArg", touchpointParameters); //$NON-NLS-1$ unconfigurationData += TouchpointInstruction.encodeAction("removeJvmArg", touchpointParameters); //$NON-NLS-1$ } String[] programArgs = advice.getProgramArguments(); for (int i = 0; i < programArgs.length; i++) if (shouldPublishProgramArg(programArgs[i]) && !programSet.contains(programArgs[i])) { if (programArgs[i].startsWith("-")) //$NON-NLS-1$ programSet.add(programArgs[i]); touchpointParameters.clear(); touchpointParameters.put("programArg", programArgs[i]); //$NON-NLS-1$ configurationData += TouchpointInstruction.encodeAction("addProgramArg", touchpointParameters); //$NON-NLS-1$ unconfigurationData += TouchpointInstruction.encodeAction("removeProgramArg", touchpointParameters); //$NON-NLS-1$ } else if (i + 1 < programArgs.length && !programArgs[i + 1].startsWith("-")) { //$NON-NLS-1$ // if we are not publishing then skip over the following arg as it is assumed to be a parameter // to this command line arg. i++; } } return new String[] {configurationData, unconfigurationData}; } /** * Publish the CUs related to the given set of bundles. This generally covers the start-level and * and whether or not the bundle is to be started. */ protected void publishBundleCUs(IPublisherInfo publisherInfo, BundleInfo[] bundles, String configSpec, IPublisherResult result) { if (bundles == null) return; String cuIdPrefix = ""; //$NON-NLS-1$ IMatchExpression filter = null; if (configSpec != null) { cuIdPrefix = createIdString(configSpec); filter = createFilterSpec(configSpec); } for (int i = 0; i < bundles.length; i++) { GeneratorBundleInfo bundle = createGeneratorBundleInfo(bundles[i], result); if (bundle == null) continue; IInstallableUnit iu = bundle.getIU(); // If there is no host, or the filters don't match, skip this one. if (iu == null || !filterMatches(iu.getFilter(), configSpec)) continue; // TODO need to factor this out into its own action if (bundle.getStartLevel() == BundleInfo.NO_LEVEL && !bundle.isMarkedAsStarted()) { // this bundle does not require any particular configuration, the plug-in default IU will handle installing it continue; } IInstallableUnit cu = null; if (this.version != null && !this.version.equals(Version.emptyVersion)) cu = BundlesAction.createBundleConfigurationUnit(bundle.getSymbolicName(), this.version, false, bundle, flavor + cuIdPrefix, filter); else cu = BundlesAction.createBundleConfigurationUnit(bundle.getSymbolicName(), Version.parseVersion(bundle.getVersion()), false, bundle, flavor + cuIdPrefix, filter); if (cu != null) { // Product Query will run against the repo, make sure these CUs are in before then // TODO review the aggressive addition to the metadata repo. perhaps the query can query the result as well. // IMetadataRepository metadataRepository = info.getMetadataRepository(); // if (metadataRepository != null) { // metadataRepository.addInstallableUnits(new IInstallableUnit[] {cu}); // } result.addIU(cu, IPublisherResult.ROOT); } } } protected GeneratorBundleInfo createGeneratorBundleInfo(BundleInfo bundleInfo, IPublisherResult result) { String name = bundleInfo.getSymbolicName(); //query for a matching IU IInstallableUnit iu = queryForIU(outerResults, name, Version.create(bundleInfo.getVersion())); if (iu != null) { if (iu.getVersion() == null) bundleInfo.setVersion("0.0.0"); //$NON-NLS-1$ else bundleInfo.setVersion(iu.getVersion().toString()); GeneratorBundleInfo newInfo = new GeneratorBundleInfo(bundleInfo); newInfo.setIU(iu); return newInfo; } if (bundleInfo.getLocation() != null || bundleInfo.getVersion() != null) return new GeneratorBundleInfo(bundleInfo); //harder: try id_version int i = name.indexOf('_'); while (i > -1) { try { Version bundleVersion = Version.parseVersion(name.substring(i)); bundleInfo.setSymbolicName(name.substring(0, i)); bundleInfo.setVersion(bundleVersion.toString()); return new GeneratorBundleInfo(bundleInfo); } catch (IllegalArgumentException e) { // the '_' found was probably part of the symbolic id i = name.indexOf('_', i); } } return null; } }