1 /*******************************************************************************
2  * Copyright (c) 2005, 2018 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.core.runtime.spi;
15 
16 import java.io.File;
17 import java.util.*;
18 import javax.xml.parsers.SAXParserFactory;
19 import org.eclipse.core.internal.registry.*;
20 import org.eclipse.core.runtime.*;
21 import org.eclipse.osgi.util.NLS;
22 
23 /**
24  * This is the basic registry strategy. It describes how the registry does logging,
25  * message translation, extra start/stop processing, event scheduling, caching,
26  * and debugging.
27  * <p>
28  * In this strategy:
29  * </p><ul>
30  * <li>Logging is done onto <code>System.out</code>;</li>
31  * <li>The translation routine assumes that keys are prefixed with <code>'%'/</code>;</li>
32  * <li>Caching is enabled and doesn't use state or time stamp validation;</li>
33  * <li>Standard Java class loading is used to create executable extensions.</li>
34  * </ul><p>
35  * This class can be used without OSGi running.
36  * </p><p>
37  * This class can be overridden and/or instantiated by clients.
38  * </p>
39  * @since org.eclipse.equinox.registry 3.2
40  */
41 public class RegistryStrategy {
42 
43 	private SAXParserFactory theXMLParserFactory = null;
44 
45 	/**
46 	 * Array of file system directories to store cache files; might be <code>null</code>
47 	 */
48 	private final File[] storageDirs;
49 
50 	/**
51 	 * Specifies if the registry file cache is read only; might be <code>null</code>
52 	 */
53 	private final boolean[] cacheReadOnly;
54 
55 	/**
56 	 * Constructor for this default registry strategy.
57 	 * <p>
58 	 * The strategy sequentially checks the array of storage directories to
59 	 * discover the location of the registry cache formed by previous invocations of the extension
60 	 * registry. Once found, the location is used to store registry cache. If this value
61 	 * is <code>null</code> then caching of the registry content is disabled.
62 	 * </p><p>
63 	 * The cache read-only array is an array the same length as the storage directory array.
64 	 * It contains boolean values indicating whether or not each storage directory is read-only.
65 	 * If the value at an index is <code>true</code> then the location at the corresponding index
66 	 * in the storage directories array is read-only; if <code>false</code> then the cache location
67 	 * is read-write. The array can be <code>null</code> if the <code>storageDirs</code> parameter
68 	 * is <code>null</code>.
69 	 * </p>
70 	 *
71 	 * @param storageDirs array of file system directories, or <code>null</code>
72 	 * @param cacheReadOnly array of read only attributes, or <code>null</code>
73 	 */
RegistryStrategy(File[] storageDirs, boolean[] cacheReadOnly)74 	public RegistryStrategy(File[] storageDirs, boolean[] cacheReadOnly) {
75 		this.storageDirs = storageDirs;
76 		this.cacheReadOnly = cacheReadOnly;
77 	}
78 
79 	/**
80 	 * Returns the number of possible cache locations for this registry.
81 	 *
82 	 * @return number of possible cache locations for this registry
83 	 */
getLocationsLength()84 	public final int getLocationsLength() {
85 		if (storageDirs == null)
86 			return 0;
87 		return storageDirs.length;
88 	}
89 
90 	/**
91 	 * Returns the possible registry cache location identified by the index.
92 	 *
93 	 * @param index index of the possible registry location
94 	 * @return potential registry cache location
95 	 */
getStorage(int index)96 	public final File getStorage(int index) {
97 		if (storageDirs != null)
98 			return storageDirs[index];
99 		return null;
100 	}
101 
102 	/**
103 	 * Returns the read-only status of the registry cache location.
104 	 *
105 	 * @param index the index of the possible registry location
106 	 * @return <code>true</code> if the location is read only and
107 	 * 	<code>false</code> if the location is read/write
108 	 */
isCacheReadOnly(int index)109 	public final boolean isCacheReadOnly(int index) {
110 		if (cacheReadOnly != null)
111 			return cacheReadOnly[index];
112 		return true;
113 	}
114 
115 	/**
116 	 * Override this method to provide customized logging functionality
117 	 * to the registry. The method adds a log entry based on the supplied status.
118 	 * <p>
119 	 * This method writes a message to <code>System.out</code>
120 	 * in the following format:</p>
121 	 * <pre>
122 	 * [Error|Warning|Log]: Main error message
123 	 * [Error|Warning|Log]: Child error message 1
124 	 * 	...
125 	 * [Error|Warning|Log]: Child error message N
126 	 * </pre>
127 	 *
128 	 * @param status the status to log
129 	 */
log(IStatus status)130 	public void log(IStatus status) {
131 		RegistrySupport.log(status, null);
132 	}
133 
134 	/**
135 	 * Translates key using the supplied resource bundle. The resource bundle is
136 	 * optional and might be <code>null</code>.
137 	 * <p>
138 	 * The default translation routine assumes that keys are prefixed with '%'. If
139 	 * no resource bundle is present, the key itself (without leading '%') is returned.
140 	 * There is no decoding for the leading '%%'.
141 	 * </p>
142 	 *
143 	 * @param key message key to be translated
144 	 * @param resources resource bundle, or <code>null</code>
145 	 * @return the translated string, must not be <code>null</code>
146 	 */
translate(String key, ResourceBundle resources)147 	public String translate(String key, ResourceBundle resources) {
148 		return RegistrySupport.translate(key, resources);
149 	}
150 
151 	/**
152 	 * Override this method to provide additional processing performed
153 	 * when the registry is created and started. Overrides should call
154 	 * <code>super.onStart()</code> at the beginning of the processing.
155 	 * <p>
156 	 * <strong>NOTE</strong>: Avoid placing duplicate functionality in
157 	 * this method and {@link #onStart(IExtensionRegistry, boolean)} as
158 	 * both methods will be called on the registry startup.
159 	 * </p>
160 	 * @param registry the extension registry being started
161 	 *
162 	 * @deprecated use {@link #onStart(IExtensionRegistry, boolean)}.
163 	 */
164 	@Deprecated
onStart(IExtensionRegistry registry)165 	public void onStart(IExtensionRegistry registry) {
166 		// The default implementation
167 	}
168 
169 	/**
170 	 * Override this method to provide additional processing performed
171 	 * when the registry is created and started. Overrides should call
172 	 * <code>super.onStart()</code> at the beginning of the processing.
173 	 *
174 	 * @param registry the extension registry being started
175 	 * @param loadedFromCache true is registry contents was loaded from
176 	 * cache when the registry was created
177 	 *
178 	 * @since 3.4
179 	 */
onStart(IExtensionRegistry registry, boolean loadedFromCache)180 	public void onStart(IExtensionRegistry registry, boolean loadedFromCache) {
181 		// The default implementation
182 	}
183 
184 	/**
185 	 * Override this method to provide additional processing to be performed
186 	 * just before the registry is stopped. Overrides should call
187 	 * <code>super.onStop()</code> at the end of the processing.
188 	 * @param registry the extension registry being stopped
189 	 */
onStop(IExtensionRegistry registry)190 	public void onStop(IExtensionRegistry registry) {
191 		// The default implementation
192 	}
193 
194 	/**
195 	 * Creates an executable extension. Override this method to supply an alternative processing
196 	 * for the creation of executable extensions.
197 	 * <p>
198 	 * This method receives the contributor of the executable extension and, possibly,
199 	 * an optional contributor name if specified by the executable extension. The overridden
200 	 * contributor name might be <code>null</code>.
201 	 * </p><p>
202 	 * In this implementation registry attempts to instantiate the class specified via
203 	 * the class name (must not be <code>null</code>) using standard Java reflection mechanism.
204 	 * This method assumes that such class has a default constructor with no arguments.
205 	 * </p>
206 	 *
207 	 * @param contributor the contributor of this executable extension
208 	 * @param className the name of the class to be instantiated
209 	 * @param overridenContributorName the contributor to be used, or <code>null</code> if not specified
210 	 * @return the object created, or <code>null</code>
211 	 * @throws CoreException if there was a problem creating the executable extension
212 	 * @see IConfigurationElement#createExecutableExtension(String)
213 	 * @see IExecutableExtension
214 	 */
createExecutableExtension(RegistryContributor contributor, String className, String overridenContributorName)215 	public Object createExecutableExtension(RegistryContributor contributor, String className, String overridenContributorName) throws CoreException {
216 		Object result = null;
217 		Class<?> classInstance = null;
218 		try {
219 			classInstance = Class.forName(className);
220 		} catch (ClassNotFoundException e1) {
221 			String message = NLS.bind(RegistryMessages.exExt_findClassError, contributor.getActualName(), className);
222 			throw new CoreException(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, IRegistryConstants.PLUGIN_ERROR, message, e1));
223 		}
224 
225 		try {
226 			result = classInstance.getDeclaredConstructor().newInstance();
227 		} catch (Exception e) {
228 			String message = NLS.bind(RegistryMessages.exExt_instantiateClassError, contributor.getActualName(), className);
229 			throw new CoreException(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, IRegistryConstants.PLUGIN_ERROR, message, e));
230 		}
231 		return result;
232 	}
233 
234 	/**
235 	 * Override this method to customize scheduling of an extension registry event. Note that this method
236 	 * <strong>must</strong> make the following call to actually process the event:
237 	 * <pre><code>
238 	 * 	RegistryStrategy.processChangeEvent(listeners, deltas, registry);
239 	 * </code></pre><p>
240 	 * In the default implementation, the method registry events are executed in a queue
241 	 * on a separate thread (i.e. asynchronously, sequentially).
242 	 * </p>
243 	 *
244 	 * @param listeners the list of active listeners (thread safe); may not be <code>null</code>
245 	 * @param deltas the registry deltas (thread safe); may not be <code>null</code>
246 	 * @param registry the extension registry (NOT thread safe); may not be <code>null</code>
247 	 */
scheduleChangeEvent(Object[] listeners, Map<String, ?> deltas, Object registry)248 	public void scheduleChangeEvent(Object[] listeners, Map<String, ?> deltas, Object registry) {
249 		((ExtensionRegistry) registry).scheduleChangeEvent(listeners, deltas);
250 	}
251 
252 	/**
253 	 * This method performs actual processing of the registry change event. It should
254 	 * only be used by overrides of the RegistryStrategy.scheduleChangeEvent. It will
255 	 * return <code>null</code> if an unexpected registry type was encountered.
256 	 *
257 	 * @param listeners the list of active listeners; may not be <code>null</code>
258 	 * @param deltas the extension registry deltas; may not be <code>null</code>
259 	 * @param registry the extension registry; may not be <code>null</code>
260 	 * @return status of the operation or <code>null</code>
261 	 */
processChangeEvent(Object[] listeners, Map<String, ?> deltas, Object registry)262 	public final static IStatus processChangeEvent(Object[] listeners, Map<String, ?> deltas, Object registry) {
263 		if (registry instanceof ExtensionRegistry)
264 			return ((ExtensionRegistry) registry).processChangeEvent(listeners, deltas);
265 		return null;
266 	}
267 
268 	/**
269 	 * Override this method to specify debug requirements to the registry. In the default
270 	 * implementation this method returns <code>false</code> indicating that debug functionality
271 	 * is turned off.
272 	 * <p>
273 	 * Note that in a general case the extension registry plug-in doesn't depend on OSGI and
274 	 * therefore cannot use Eclipse .options files to discover debug options.
275 	 * </p>
276 	 *
277 	 * @return <code>true</code> if debug logging and validation should be performed and
278 	 * 	<code>false</code> otherwise
279 	 */
debug()280 	public boolean debug() {
281 		return false;
282 	}
283 
284 	/**
285 	 * Override this method to specify debug requirements for the registry event processing.
286 	 * In the default implementation this method returns <code>false</code> indicating that
287 	 * debug of the registry events is turned off.
288 	 * <p>
289 	 * Note that in a general case the extension registry plug-in doesn't depend on OSGI and
290 	 * therefore cannot use Eclipse .options files to discover debug options.
291 	 * </p>
292 	 *
293 	 * @return <code>true</code> if debug logging and validation of the registry events
294 	 * 	should be performed and <code>false</code> otherwise
295 	 */
debugRegistryEvents()296 	public boolean debugRegistryEvents() {
297 		return false;
298 	}
299 
300 	/**
301 	 * Specifies if the extension registry should use cache to store registry data between
302 	 * invocations.
303 	 * <p>
304 	 * The default implementation enables caching returning <code>true</code>.
305 	 * </p>
306 	 *
307 	 * @return <code>true</code> if the cache should be used and <code>false</code> otherwise
308 	 */
cacheUse()309 	public boolean cacheUse() {
310 		return true;
311 	}
312 
313 	/**
314 	 * Specifies if lazy cache loading is used.
315 	 * <p>
316 	 * The default implementation specifies that lazy cache loading is going to be used
317 	 * and therefore returns <code>true</code>.
318 	 * </p>
319 	 *
320 	 * @return <code>true</code> if lazy cache loading is used and <code>false</code> otherwise
321 	 */
cacheLazyLoading()322 	public boolean cacheLazyLoading() {
323 		return true;
324 	}
325 
326 	/**
327 	 * This method is called as a part of the registry cache validation. The cache is valid
328 	 * on the registry startup if the pair {container time stamp, contributors time stamp}
329 	 * supplied by the registry strategy is the same as the {container time stamp, contributors time stamp}
330 	 * stored in the registry cache. The goal of the validation is to be able to catch modifications
331 	 * made to the original data contributed into the registry and not reflected in the registry cache.
332 	 * <p>
333 	 * The method produces a number that corresponds to the current state of the data stored
334 	 * by the container. Increment the stamp if the data stored in the container has been updated
335 	 * so that the data cached by the registry is no longer valid. For instance, in Eclipse addition
336 	 * or removal of a bundle results in the number returned by this method being incremented. As a result,
337 	 * if a bundle that contributed plugin.xml into the extension registry was modified, the state doesn't
338 	 * match the state stored in the registry cache. In this case the cache content becomes invalid and
339 	 * the registry needs to be re-created from the original data.
340 	 * </p><p>
341 	 * Generally, treat this number as a hash code for the data stored in the registry.
342 	 * It stays the same as long as the registry content is not changing. It becomes a different
343 	 * number as the registry content gets modified.
344 	 * </p><p>
345 	 * Return 0 to indicate that state verification is not required.
346 	 * </p>
347 	 *
348 	 * @return number indicating state of the application data
349 	 */
getContainerTimestamp()350 	public long getContainerTimestamp() {
351 		return 0;
352 	}
353 
354 	/**
355 	 * This method is called as a part of the registry cache validation. The method calculates
356 	 * a number describing the time when the originating contributions (i.e., plugin.xml files
357 	 * in case of the Eclipse registry) were last modified.
358 	 * <p>
359 	 * The value returned by the method is compared with the timestamp tracked by the registry.
360 	 * If contributions changed since they have been added to the registry (i.e., plugin.xml
361 	 * file was modified since the last run), the value of the {@link #getContributionsTimestamp()}
362 	 * will change and no longer will be the same as the value tracked by the registry. In this case
363 	 * the cache is considered to be invalid and the registry is going to be re-populated form scratch.
364 	 * </p><p>
365 	 * (The word "timestamp" is used very loosely here. In this context, "timestamp" is more likely
366 	 * to be a hash value aggregating a number of actual timestamps from the contributions.)
367 	 * </p><p>
368 	 * This method may return 0 to indicate that no time stamp verification is required.
369 	 * </p>
370 	 * @return a value corresponding to the last modification time of contributions contained
371 	 * in the registry
372 	 */
getContributionsTimestamp()373 	public long getContributionsTimestamp() {
374 		return 0;
375 	}
376 
377 	/**
378 	 * Returns the parser used by the registry to parse descriptions of extension points and extensions.
379 	 * This method must not return <code>null</code>.
380 	 *
381 	 * @return this strategy's parser
382 	 * @see org.eclipse.core.runtime.IExtensionRegistry#addContribution(java.io.InputStream, IContributor, boolean, String, ResourceBundle, Object)
383 	 */
getXMLParser()384 	public SAXParserFactory getXMLParser() {
385 		if (theXMLParserFactory == null)
386 			theXMLParserFactory = SAXParserFactory.newInstance();
387 		return theXMLParserFactory;
388 	}
389 
390 	/**
391 	 * Translates array of keys supplied by the contributor to the requested locale.
392 	 * <p>
393 	 * This method is intended to be overridden by specialized registry strategies
394 	 * that know translation conventions for the contributors, for instance,
395 	 * the agreed upon locations of the translated keys for bundle contributors.
396 	 * The base implementation simply returns the array of non-translated keys.
397 	 * </p><p>
398 	 * This method is only used if multi-language support is enabled.
399 	 * </p>
400 	 * @param nonTranslated message keys to be translated
401 	 * @param contributor the contributor of the keys to be translated
402 	 * @param locale the requested locale for the keys
403 	 * @return the arrays of translated strings
404 	 * @see IExtensionRegistry#isMultiLanguage()
405 	 * @since org.eclipse.equinox.registry 3.5
406 	 */
translate(String[] nonTranslated, IContributor contributor, String locale)407 	public String[] translate(String[] nonTranslated, IContributor contributor, String locale) {
408 		return nonTranslated;
409 	}
410 
411 	/**
412 	 * Returns the current locale for the extension registry with enabled
413 	 * multi-language support.
414 	 * <p>
415 	 * The default implementation assumes that there is a single system wide
416 	 * locale, equivalent to {@link Locale#getDefault()}.
417 	 * </p><p>
418 	 * The result of this method should not be retained or passed to other threads.
419 	 * The current locale can change any time and may be different for each thread.
420 	 * </p><p>
421 	 * This method can be overridden by subclasses that wish to provide a way
422 	 * to change the default locale.
423 	 * </p><p>
424 	 * This method is only used if multi-language support is enabled.
425 	 * </p>
426 	 * @see IExtensionRegistry#isMultiLanguage()
427 	 * @return the default locale
428 	 * @since org.eclipse.equinox.registry 3.5
429 	 */
getLocale()430 	public String getLocale() {
431 		return Locale.getDefault().toString();
432 	}
433 }
434