1 /*******************************************************************************
2  * Copyright (c) 2007, 2017 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *******************************************************************************/
14 package org.eclipse.pde.internal.core;
15 
16 import java.io.BufferedInputStream;
17 import java.io.File;
18 import java.io.FileInputStream;
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.util.zip.ZipEntry;
22 import java.util.zip.ZipFile;
23 import javax.xml.parsers.SAXParserFactory;
24 import org.eclipse.core.runtime.IContributor;
25 import org.eclipse.core.runtime.IExtensionRegistry;
26 import org.eclipse.core.runtime.IStatus;
27 import org.eclipse.core.runtime.spi.IDynamicExtensionRegistry;
28 import org.eclipse.core.runtime.spi.RegistryContributor;
29 import org.eclipse.core.runtime.spi.RegistryStrategy;
30 import org.eclipse.osgi.service.resolver.BundleDescription;
31 import org.eclipse.osgi.service.resolver.HostSpecification;
32 import org.eclipse.pde.core.plugin.IPluginModelBase;
33 import org.eclipse.pde.core.plugin.ModelEntry;
34 import org.eclipse.pde.core.plugin.PluginRegistry;
35 import org.osgi.util.tracker.ServiceTracker;
36 
37 public class PDERegistryStrategy extends RegistryStrategy {
38 
39 	/**
40 	 * Tracker for the XML parser service
41 	 */
42 	private ServiceTracker<?, SAXParserFactory> xmlTracker = null;
43 
44 	private Object fKey = null;
45 
46 	private ModelListener fModelListener = null;
47 	private ExtensionListener fExtensionListener = null;
48 	private PDEExtensionRegistry fPDERegistry = null;
49 
50 	class RegistryListener {
51 		IExtensionRegistry fRegistry;
52 
removeModels(IPluginModelBase[] bases, boolean onlyInactive)53 		protected final void removeModels(IPluginModelBase[] bases, boolean onlyInactive) {
54 			for (IPluginModelBase base : bases) {
55 				//				resetModel(bases[i]);
56 				if (onlyInactive && base.isEnabled()) {
57 					continue;
58 				}
59 				removeBundle(fRegistry, base);
60 			}
61 		}
62 
setRegistry(IExtensionRegistry registry)63 		public void setRegistry(IExtensionRegistry registry) {
64 			fRegistry = registry;
65 		}
66 	}
67 
68 	class ModelListener extends RegistryListener implements IPluginModelListener {
69 
70 		@Override
modelsChanged(PluginModelDelta delta)71 		public void modelsChanged(PluginModelDelta delta) {
72 			if (fRegistry == null) {
73 				createRegistry();
74 			}
75 			// can ignore removed models since the ModelEntries is empty
76 			ModelEntry[] entries = delta.getChangedEntries();
77 			for (int i = 0; i < entries.length; i++) {
78 				// If we have workspace models, we need to make sure they are registered before external models so when we search for extension points,
79 				// we find the workspace version
80 				IPluginModelBase[] workspaceModels = entries[i].getWorkspaceModels();
81 				if (workspaceModels.length > 0) {
82 					removeModels(entries[i].getExternalModels(), !entries[i].hasWorkspaceModels());
83 					removeModels(workspaceModels, true);
84 					addBundles(fRegistry, entries[i].getWorkspaceModels());
85 				}
86 				// make sure the external models are registered at all times
87 				addBundles(fRegistry, entries[i].getExternalModels());
88 			}
89 			entries = delta.getAddedEntries();
90 			ModelEntry[] removedEntries = delta.getRemovedEntries();
91 			if (removedEntries.length == entries.length && fRegistry instanceof IDynamicExtensionRegistry) {
92 				for (ModelEntry entry : removedEntries) {
93 					if (entry.getId() != null) {
94 						IDynamicExtensionRegistry registry = (IDynamicExtensionRegistry) fRegistry;
95 						IContributor[] contributors = registry.getAllContributors();
96 						for (IContributor contributor : contributors) {
97 							if (entry.getId().equals(contributor.getName())) {
98 								registry.removeContributor(contributor, fKey);
99 								break;
100 							}
101 						}
102 					}
103 				}
104 			}
105 			for (ModelEntry entry : entries) {
106 				addBundles(fRegistry, entry.getActiveModels());
107 			}
108 		}
109 
110 	}
111 
112 	class ExtensionListener extends RegistryListener implements IExtensionDeltaListener {
113 
114 		@Override
extensionsChanged(IExtensionDeltaEvent event)115 		public void extensionsChanged(IExtensionDeltaEvent event) {
116 			if (fRegistry == null) {
117 				createRegistry();
118 			}
119 			IPluginModelBase[] bases = event.getRemovedModels();
120 			removeModels(bases, false);
121 			removeModels(event.getChangedModels(), false);
122 			addBundles(fRegistry, event.getChangedModels());
123 			addBundles(fRegistry, event.getAddedModels());
124 			// if we remove the last workspace model for a Bundle-SymbolicName, then refresh the external models by removing then adding them
125 			for (IPluginModelBase base : bases) {
126 				ModelEntry entry = PluginRegistry.findEntry(base.getPluginBase().getId());
127 				if (entry != null && entry.getWorkspaceModels().length == 0) {
128 					IPluginModelBase[] externalModels = entry.getExternalModels();
129 					removeModels(externalModels, false);
130 					addBundles(fRegistry, externalModels);
131 				}
132 			}
133 		}
134 
135 	}
136 
PDERegistryStrategy(File[] storageDirs, boolean[] cacheReadOnly, Object key, PDEExtensionRegistry registry)137 	public PDERegistryStrategy(File[] storageDirs, boolean[] cacheReadOnly, Object key, PDEExtensionRegistry registry) {
138 		super(storageDirs, cacheReadOnly);
139 		init();
140 		fKey = key;
141 		fPDERegistry = registry;
142 	}
143 
init()144 	protected void init() {
145 		connectListeners();
146 	}
147 
148 	@Override
log(IStatus status)149 	public void log(IStatus status) {
150 		// Because we are at development time, we create markers for registry problems and therefore do not log anything (bug 330648)
151 	}
152 
connectListeners()153 	protected void connectListeners() {
154 		// Listen for model changes to register new bundles and unregister removed bundles
155 		PluginModelManager manager = PDECore.getDefault().getModelManager();
156 		manager.addPluginModelListener(fModelListener = new ModelListener());
157 		manager.addExtensionDeltaListener(fExtensionListener = new ExtensionListener());
158 	}
159 
setListenerRegistry(IExtensionRegistry registry)160 	protected void setListenerRegistry(IExtensionRegistry registry) {
161 		if (fModelListener != null) {
162 			fModelListener.setRegistry(registry);
163 		}
164 		if (fExtensionListener != null) {
165 			fExtensionListener.setRegistry(registry);
166 		}
167 	}
168 
169 	@Override
onStart(IExtensionRegistry registry, boolean loadedFromCache)170 	public void onStart(IExtensionRegistry registry, boolean loadedFromCache) {
171 		super.onStart(registry, loadedFromCache);
172 		setListenerRegistry(registry);
173 		if (!loadedFromCache) {
174 			processBundles(registry);
175 		}
176 	}
177 
178 	@Override
onStop(IExtensionRegistry registry)179 	public void onStop(IExtensionRegistry registry) {
180 		super.onStop(registry);
181 		setListenerRegistry(null);
182 	}
183 
184 	@Override
getXMLParser()185 	public SAXParserFactory getXMLParser() {
186 		if (xmlTracker == null) {
187 			xmlTracker = new ServiceTracker<>(PDECore.getDefault().getBundleContext(), SAXParserFactory.class, null);
188 			xmlTracker.open();
189 		}
190 		return xmlTracker.getService();
191 	}
192 
processBundles(IExtensionRegistry registry)193 	private void processBundles(IExtensionRegistry registry) {
194 		addBundles(registry, fPDERegistry.getModels());
195 	}
196 
addBundles(IExtensionRegistry registry, IPluginModelBase[] bases)197 	private void addBundles(IExtensionRegistry registry, IPluginModelBase[] bases) {
198 		for (IPluginModelBase base : bases) {
199 			addBundle(registry, base);
200 		}
201 	}
202 
addBundle(IExtensionRegistry registry, IPluginModelBase base)203 	private void addBundle(IExtensionRegistry registry, IPluginModelBase base) {
204 		IContributor contributor = createContributor(base);
205 		if (contributor == null) {
206 			return;
207 		}
208 		if (((IDynamicExtensionRegistry) registry).hasContributor(contributor)) {
209 			return;
210 		}
211 
212 		File input = getFile(base);
213 		if (input == null) {
214 			return;
215 		}
216 		InputStream is = null;
217 		ZipFile jfile = null;
218 
219 		try {
220 			if (new File(base.getInstallLocation()).isDirectory()) {
221 				// Directory bundle, access the extensions file directly
222 				is = new FileInputStream(input);
223 			} else {
224 				// Archived bundle, need to extract the file
225 				jfile = new ZipFile(input, ZipFile.OPEN_READ);
226 				String fileName = (base.isFragmentModel()) ? ICoreConstants.FRAGMENT_FILENAME_DESCRIPTOR
227 						: ICoreConstants.PLUGIN_FILENAME_DESCRIPTOR;
228 				ZipEntry entry = jfile.getEntry(fileName);
229 				if (entry != null) {
230 					is = jfile.getInputStream(entry);
231 				}
232 			}
233 			if (is != null) {
234 				registry.addContribution(new BufferedInputStream(is), contributor, true, input.getPath(), null, fKey);
235 			}
236 		} catch (IOException e) {
237 		} finally {
238 			if (jfile != null) {
239 				try {
240 					jfile.close();
241 				} catch (IOException e) {
242 				}
243 			}
244 		}
245 	}
246 
removeBundle(IExtensionRegistry registry, IPluginModelBase base)247 	private void removeBundle(IExtensionRegistry registry, IPluginModelBase base) {
248 		if (registry instanceof IDynamicExtensionRegistry) {
249 			IContributor contributor = createContributor(base);
250 			if (contributor != null && ((IDynamicExtensionRegistry) registry).hasContributor(contributor)) {
251 				((IDynamicExtensionRegistry) registry).removeContributor(createContributor(base), fKey);
252 			}
253 		}
254 	}
255 
256 	//	added for releasing cached information from IPluginModelBase
257 	//	private void resetModel(IPluginModelBase model) {
258 	//		IPluginBase base = model.getPluginBase();
259 	//		if (base instanceof BundlePluginBase) {
260 	//			IExtensions ext = ((BundlePluginBase)base).getExtensionsRoot();
261 	//			if (ext != null && ext instanceof AbstractExtensions) {
262 	//				((AbstractExtensions)ext).reset();
263 	//			}
264 	//		} else if (base instanceof AbstractExtensions){
265 	//			((AbstractExtensions)base).resetExtensions();
266 	//		}
267 	//	}
268 
getFile(IPluginModelBase base)269 	private File getFile(IPluginModelBase base) {
270 		String loc = base.getInstallLocation();
271 		if (loc == null) {
272 			return null;
273 		}
274 		File file = new File(loc);
275 		if (!file.exists()) {
276 			return null;
277 		}
278 		if (file.isFile()) {
279 			return file;
280 		}
281 		String fileName = (base.isFragmentModel()) ? ICoreConstants.FRAGMENT_FILENAME_DESCRIPTOR : ICoreConstants.PLUGIN_FILENAME_DESCRIPTOR;
282 		File inputFile = new File(file, fileName);
283 		return (inputFile.exists()) ? inputFile : null;
284 	}
285 
createContributor(IPluginModelBase base)286 	public IContributor createContributor(IPluginModelBase base) {
287 		BundleDescription desc = base == null ? null : base.getBundleDescription();
288 		// return null if the IPluginModelBase does not have a BundleDescription (since then we won't have a valid 'id')
289 		if (desc == null) {
290 			return null;
291 		}
292 		String name = desc.getSymbolicName();
293 		String id = Long.toString(desc.getBundleId());
294 		String hostName = null;
295 		String hostId = null;
296 
297 		HostSpecification host = desc.getHost();
298 		// make sure model is a singleton.  If it is a fragment, make sure host is singleton
299 		if (host != null && host.getBundle() != null && !host.getBundle().isSingleton() || host == null && !desc.isSingleton()) {
300 			return null;
301 		}
302 		if (host != null) {
303 			BundleDescription[] hosts = host.getHosts();
304 			if (hosts.length != 1) {
305 				return null;
306 			}
307 			BundleDescription hostDesc = hosts[0];
308 			hostName = hostDesc.getSymbolicName();
309 			hostId = Long.toString(hostDesc.getBundleId());
310 		}
311 		return new RegistryContributor(id, name, hostId, hostName);
312 	}
313 
dispose()314 	public void dispose() {
315 		PluginModelManager manager = PDECore.getDefault().getModelManager();
316 		manager.removePluginModelListener(fModelListener);
317 		manager.removeExtensionDeltaListener(fExtensionListener);
318 		if (xmlTracker != null) {
319 			xmlTracker.close();
320 			xmlTracker = null;
321 		}
322 	}
323 
createRegistry()324 	private void createRegistry() {
325 		fPDERegistry.getRegistry();
326 	}
327 
328 	// Same timestamp calculations as PDEState.computeTimestamp(URL[] urls, long timestamp)
329 	@Override
getContributionsTimestamp()330 	public long getContributionsTimestamp() {
331 		IPluginModelBase[] bases = fPDERegistry.getModels();
332 		long timeStamp = 0;
333 		for (IPluginModelBase base : bases) {
334 			String loc = base.getInstallLocation();
335 			if (loc == null) {
336 				continue;
337 			}
338 
339 			File location = new File(loc);
340 			if (location.exists()) {
341 				if (location.isFile()) {
342 					timeStamp ^= location.lastModified();
343 				} else {
344 					File manifest = new File(location, ICoreConstants.BUNDLE_FILENAME_DESCRIPTOR);
345 					if (manifest.exists()) {
346 						timeStamp ^= manifest.lastModified();
347 					}
348 					manifest = new File(location, ICoreConstants.PLUGIN_FILENAME_DESCRIPTOR);
349 					if (manifest.exists()) {
350 						timeStamp ^= manifest.lastModified();
351 					}
352 					manifest = new File(location, ICoreConstants.FRAGMENT_FILENAME_DESCRIPTOR);
353 					if (manifest.exists()) {
354 						timeStamp ^= manifest.lastModified();
355 					}
356 				}
357 				timeStamp ^= location.getAbsolutePath().hashCode();
358 			}
359 		}
360 		return timeStamp;
361 	}
362 
363 }
364