Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 823000c2b83f8c152d4e0dfe5e485259c5b47600 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
/*******************************************************************************
 * Copyright (c) 2008, 2017 Code 9 and others.
 *
 * This
 * program and the accompanying materials are made available under the terms of
 * the Eclipse Public License 2.0 which accompanies this distribution, and is
 * available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 * 
 * 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<String> PROPERTIES_TO_SKIP;
	private static HashSet<String> 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 (String configSpec1 : configSpecs) {
			if (monitor.isCanceled())
				return Status.CANCEL_STATUS;
			String configSpec = configSpec1;
			Collection<IConfigAdvice> configAdvice = publisherInfo.getAdvice(configSpec, false, id, version, IConfigAdvice.class);
			BundleInfo[] bundles = fillInBundles(configAdvice, results);
			publishBundleCUs(publisherInfo, bundles, configSpec, innerResult);
			publishConfigIUs(configAdvice, innerResult, configSpec);
			Collection<IExecutableAdvice> 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<? extends IVersionedId> 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<IConfigAdvice> configAdvice, IPublisherResult results) {
		ArrayList<BundleInfo> result = new ArrayList<>();
		for (IConfigAdvice advice : configAdvice) {

			int defaultStart = BundleInfo.NO_LEVEL;
			Map<String, String> 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 (BundleInfo bundleInfo : bundles) {
				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(bundleInfo);
				} else {
					try {
						File location = new File(bundleInfo.getLocation());
						Dictionary<String, String> 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<IExecutableAdvice> 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<String, String> 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<IConfigAdvice> 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<String, String> 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<String, String> 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<IConfigAdvice> configAdvice) {
		String configurationData = ""; //$NON-NLS-1$
		String unconfigurationData = ""; //$NON-NLS-1$
		Set<String> properties = new HashSet<>();
		for (IConfigAdvice advice : configAdvice) {
			for (Entry<String, String> aProperty : advice.getProperties().entrySet()) {
				String key = aProperty.getKey();
				if (shouldPublishProperty(key) && !properties.contains(key)) {
					properties.add(key);
					Map<String, String> 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<String, String> 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<IExecutableAdvice> launchingAdvice) {
		String configurationData = ""; //$NON-NLS-1$
		String unconfigurationData = ""; //$NON-NLS-1$

		Map<String, String> touchpointParameters = new LinkedHashMap<>();
		Set<String> jvmSet = new HashSet<>();
		Set<String> programSet = new HashSet<>();
		for (IExecutableAdvice advice : launchingAdvice) {
			String[] jvmArgs = advice.getVMArguments();
			for (String jvmArg : jvmArgs) {
				if (shouldPublishJvmArg(jvmArg) && !jvmSet.contains(jvmArg)) {
					jvmSet.add(jvmArg);
					touchpointParameters.clear();
					touchpointParameters.put("jvmArg", jvmArg); //$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<IInstallableUnit> filter = null;
		if (configSpec != null) {
			cuIdPrefix = createIdString(configSpec);
			filter = createFilterSpec(configSpec);
		}

		for (BundleInfo bundle1 : bundles) {
			GeneratorBundleInfo bundle = createGeneratorBundleInfo(bundle1, 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;
			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;
	}

}

Back to the top