Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 36f4e79426a5eaada315d2e483a6460cc7c82562 (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
/*******************************************************************************
 * Copyright (c) 2003, 2018 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.core.internal.registry.osgi;

import java.io.*;
import java.net.URL;
import java.util.*;
import org.eclipse.core.internal.registry.ExtensionRegistry;
import org.eclipse.core.internal.registry.RegistryMessages;
import org.eclipse.core.internal.runtime.ResourceTranslator;
import org.eclipse.core.internal.runtime.RuntimeLog;
import org.eclipse.core.runtime.*;
import org.eclipse.osgi.util.ManifestElement;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.*;

/**
 * A listener for bundle events.  When a bundles come and go we look to see
 * if there are any extensions or extension points and update the registry accordingly.
 * Using a Synchronous listener here is important. If the
 * bundle activator code tries to access the registry to get its extension
 * points, we need to ensure that they are in the registry before the
 * bundle start is called. By listening sync we are able to ensure that
 * happens.
 */
public class EclipseBundleListener implements SynchronousBundleListener {
	private static final String PLUGIN_MANIFEST = "plugin.xml"; //$NON-NLS-1$
	private static final String FRAGMENT_MANIFEST = "fragment.xml"; //$NON-NLS-1$

	private final ExtensionRegistry registry;
	private final RegistryStrategyOSGI strategy;
	private final Object token;
	private final HashMap<String, Long> dynamicAddStateStamps = new HashMap<>();
	private final long currentStateStamp[] = new long[] {0};

	public EclipseBundleListener(ExtensionRegistry registry, Object key, RegistryStrategyOSGI strategy) {
		this.registry = registry;
		this.token = key;
		this.strategy = strategy;
	}

	@Override
	public void bundleChanged(BundleEvent event) {
		/* Only should listen for RESOLVED and UNRESOLVED events.
		 *
		 * When a bundle is updated the Framework will publish an UNRESOLVED and
		 * then a RESOLVED event which should cause the bundle to be removed
		 * and then added back into the registry.
		 *
		 * When a bundle is uninstalled the Framework should publish an UNRESOLVED
		 * event and then an UNINSTALLED event so the bundle will have been removed
		 * by the UNRESOLVED event before the UNINSTALLED event is published.
		 *
		 * When a bundle is refreshed from PackageAdmin an UNRESOLVED event will be
		 * published which will remove the bundle from the registry.  If the bundle
		 * can be RESOLVED after a refresh then a RESOLVED event will be published
		 * which will add the bundle back.  This is required because the classloader
		 * will have been refreshed for the bundle so all extensions and extension
		 * points for the bundle must be refreshed.
		 */
		Bundle bundle = event.getBundle();
		switch (event.getType()) {
			case BundleEvent.RESOLVED :
				synchronized (currentStateStamp) {
					long newStateStamp = registry.computeState();
					if (currentStateStamp[0] != newStateStamp) {
						// new state stamp; clear the dynamicaddStateStamps
						currentStateStamp[0] = newStateStamp;
						dynamicAddStateStamps.clear();
					}
				}
				addBundle(bundle, true);
				break;
			case BundleEvent.UNRESOLVED :
				removeBundle(bundle);
				break;
		}
	}

	public void processBundles(Bundle[] bundles) {
		for (int i = 0; i < bundles.length; i++) {
			if (isBundleResolved(bundles[i]))
				addBundle(bundles[i], false);
			else
				removeBundle(bundles[i]);
		}
	}

	private boolean isBundleResolved(Bundle bundle) {
		return (bundle.getState() & (Bundle.RESOLVED | Bundle.ACTIVE | Bundle.STARTING | Bundle.STOPPING)) != 0;
	}

	private void removeBundle(Bundle bundle) {
		long timestamp = 0;
		if (strategy.checkContributionsTimestamp()) {
			URL pluginManifest = getExtensionURL(bundle, false);
			if (pluginManifest != null)
				timestamp = strategy.getExtendedTimestamp(bundle, pluginManifest);
		}
		registry.remove(Long.toString(bundle.getBundleId()), timestamp);
	}

	static public URL getExtensionURL(Bundle bundle, boolean report) {
		// bail out if the bundle does not have a symbolic name
		if (bundle.getSymbolicName() == null)
			return null;

		boolean isFragment = OSGIUtils.getDefault().isFragment(bundle);
		String manifestName = isFragment ? FRAGMENT_MANIFEST : PLUGIN_MANIFEST;
		URL extensionURL = bundle.getEntry(manifestName);
		if (extensionURL == null)
			return null;

		// If the bundle is not a singleton, then it is not added
		if (!isSingleton(bundle)) {
			if (report && !isGeneratedManifest(bundle)) {
				String message = NLS.bind(RegistryMessages.parse_nonSingleton, bundle.getSymbolicName());
				RuntimeLog.log(new Status(IStatus.WARNING, RegistryMessages.OWNER_NAME, 0, message, null));
			}
			return null;
		}
		if (!isFragment)
			return extensionURL;

		// If the bundle is a fragment being added to a non singleton host, then it is not added
		Bundle[] hosts = OSGIUtils.getDefault().getHosts(bundle);
		if (hosts == null)
			return null; // should never happen?

		if (isSingleton(hosts[0]))
			return extensionURL;

		if (report) {
			// if the host is not a singleton we always report the error; even if the host has a generated manifest
			String message = NLS.bind(RegistryMessages.parse_nonSingletonFragment, bundle.getSymbolicName(), hosts[0].getSymbolicName());
			RuntimeLog.log(new Status(IStatus.WARNING, RegistryMessages.OWNER_NAME, 0, message, null));
		}
		return null;
	}

	private static boolean isGeneratedManifest(Bundle bundle) {
		return bundle.getHeaders("").get("Generated-from") != null; //$NON-NLS-1$ //$NON-NLS-2$
	}

	private void addBundle(Bundle bundle, boolean checkNLSFragments) {
		if (checkNLSFragments)
			checkForNLSFragment(bundle);
		// if the given bundle already exists in the registry then return.
		// note that this does not work for update cases.
		IContributor contributor = ContributorFactoryOSGi.createContributor(bundle);
		if (registry.hasContributor(contributor))
			return;
		URL pluginManifest = getExtensionURL(bundle, true);
		if (pluginManifest == null)
			return;
		InputStream is;
		try {
			is = new BufferedInputStream(pluginManifest.openStream());
		} catch (IOException ex) {
			is = null;
		}
		if (is == null)
			return;

		ResourceBundle translationBundle = null;
		try {
			translationBundle = ResourceTranslator.getResourceBundle(bundle);
		} catch (MissingResourceException e) {
			//Ignore the exception
		}
		long timestamp = 0;
		if (strategy.checkContributionsTimestamp())
			timestamp = strategy.getExtendedTimestamp(bundle, pluginManifest);
		registry.addContribution(is, contributor, true, pluginManifest.getPath(), translationBundle, token, timestamp);
	}

	private void checkForNLSFragment(Bundle bundle) {
		if (!OSGIUtils.getDefault().isFragment(bundle)) {
			// only need to worry about fragments
			synchronized (currentStateStamp) {
				// mark this host as processed for the current state stamp.
				dynamicAddStateStamps.put(Long.toString(bundle.getBundleId()), Long.valueOf(currentStateStamp[0]));
			}
			return;
		}
		Bundle[] hosts = OSGIUtils.getDefault().getHosts(bundle);
		if (hosts == null)
			return;
		// check to see if the hosts should be refreshed because the fragment contains NLS properties files.
		for (int i = 0; i < hosts.length; i++)
			checkForNLSFiles(hosts[i], bundle);
	}

	private void checkForNLSFiles(Bundle host, Bundle fragment) {
		String hostID = Long.toString(host.getBundleId());

		synchronized (currentStateStamp) {
			Long hostStateStamp = dynamicAddStateStamps.get(hostID);
			if (hostStateStamp != null && currentStateStamp[0] == hostStateStamp.longValue())
				return; // already processed this host
		}

		Bundle[] fragments = OSGIUtils.getDefault().getFragments(host);
		boolean refresh = false;
		// check host first
		if (hasNLSFilesFor(host, fragment)) {
			refresh = true;
		} else {
			// check the fragment provides NLS for other fragments of this host
			for (int i = 0; i < fragments.length && !refresh; i++) {
				if (fragment.equals(fragments[i]))
					continue; // skip fragment that was just resolved; it will be added in by the caller
				if (hasNLSFilesFor(fragments[i], fragment)) {
					refresh = true;
				}
			}
		}
		if (refresh) {
			// force the host and fragments to be removed and added back
			removeBundle(host);
			addBundle(host, false);
			for (int i = 0; i < fragments.length; i++) {
				if (fragment.equals(fragments[i]))
					continue; // skip fragment that was just resolved; it will be added in by the caller
				removeBundle(fragments[i]);
				addBundle(fragments[i], false);
			}
			synchronized (currentStateStamp) {
				// mark this host as processed for the current state stamp.
				dynamicAddStateStamps.put(hostID, Long.valueOf(currentStateStamp[0]));
			}
		}
	}

	private boolean hasNLSFilesFor(Bundle target, Bundle fragment) {
		if (!registry.hasContributor(Long.toString(target.getBundleId())))
			return false;
		// get the base localization path from the target
		Dictionary<?, ?> targetHeaders = target.getHeaders(""); //$NON-NLS-1$
		String localization = (String) targetHeaders.get(Constants.BUNDLE_LOCALIZATION);
		if (localization == null)
			// localization may be empty in which case we should check the default
			localization = Constants.BUNDLE_LOCALIZATION_DEFAULT_BASENAME;
		// we do a simple check to make sure the default nls path exists in the target;
		// this is for performance reasons, but I'm not sure it is valid because a target could ship without the default nls properties file but this seems very unlikely
		URL baseNLS = target.getEntry(localization + ".properties"); //$NON-NLS-1$
		if (baseNLS == null)
			return false;
		int lastSlash = localization.lastIndexOf('/');
		if (lastSlash == localization.length() - 1)
			return false; // just to be safe
		String baseDir = lastSlash < 0 ? "" : localization.substring(0, lastSlash); //$NON-NLS-1$
		String filePattern = (lastSlash < 0 ? localization : localization.substring(lastSlash + 1)) + "_*.properties"; //$NON-NLS-1$
		Enumeration<?> nlsFiles = fragment.findEntries(baseDir, filePattern, false);
		return nlsFiles != null;
	}

	private static boolean isSingleton(Bundle bundle) {
		Dictionary<?, ?> allHeaders = bundle.getHeaders(""); //$NON-NLS-1$
		String symbolicNameHeader = (String) allHeaders.get(Constants.BUNDLE_SYMBOLICNAME);
		try {
			if (symbolicNameHeader != null) {
				ManifestElement[] symbolicNameElements = ManifestElement.parseHeader(Constants.BUNDLE_SYMBOLICNAME, symbolicNameHeader);
				if (symbolicNameElements.length > 0) {
					String singleton = symbolicNameElements[0].getDirective(Constants.SINGLETON_DIRECTIVE);
					if (singleton == null)
						singleton = symbolicNameElements[0].getAttribute(Constants.SINGLETON_DIRECTIVE);

					if (!"true".equalsIgnoreCase(singleton)) { //$NON-NLS-1$
						String manifestVersion = (String) allHeaders.get(org.osgi.framework.Constants.BUNDLE_MANIFESTVERSION);
						if (manifestVersion == null) {//the header was not defined for previous versions of the bundle
							//3.0 bundles without a singleton attributes are still being accepted
							if (OSGIUtils.getDefault().getBundle(symbolicNameElements[0].getValue()) == bundle)
								return true;
						}
						return false;
					}
				}
			}
		} catch (BundleException e1) {
			//This can't happen because the fwk would have rejected the bundle
		}
		return true;
	}
}

Back to the top