Class ExtensionManager<T extends AppExtension>
- Type Parameters:
T- Any class that implements AppExtension - this is the class we'll scan for.
Your extension should bundle all required code and resources into a jar file, and include an extInfo.json file in the jar resources. We scan for that file in this class and parse it out to see if a) it exists, and b) if the version requirements in the candidate extension jar are met. See extractExtInfo for more details.
- Since:
- 2023-11-11
- Author:
- scorbo2
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprotected classThis is used internally to combine a source jar file, the extension that it contained, and an isEnabled status flag into one handy location. -
Field Summary
Fields -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionvoidSends an onActivate() message to each enabled extension, to let them know that we're starting up - use deactivateAll() to signal shutdown.voidaddExtension(T extension, boolean isEnabled) Programmatically adds an extension to our list - this was originally intended for testing purposes, but might be useful as a way for applications to supply built-in extensions without having to package them in separate jar files with the distribution.voidSends an onDeactivate() message to each enabled extension, to let them know that we're shutting down - use activateAll() to signal startup.extractExtInfo(File jarFile) Invoked internally to look for an extInfo.json file inside the given jar file and attempt to parse an AppExtensionInfo object out of it.findCandidateExtensionJars(File directory, String appName, String requiredVersion) Scans the given directory looking for Jar files that contain an extInfo.json file, and if one is found, will check its parameters against the given appName and requiredVersion to make sure the extension would work for that application.Invoke this to interrogate each enabled extension for their config properties, if any, and return them in a list.Returns a list of all loaded extensions - beware that this method will return extensions even if they are marked as disabled!protected List<ExtensionManager<T>.ExtensionWrapper> Invoked internally to return a list of all loaded extension wrappers, sorted by the extension name.Returns a list of all loaded extensions that are marked as enabled.getLoadedExtension(String className) Returns the extension instance for the given class name, if one exists.intReports how many extensions have been loaded.getSourceJar(String className) Returns the source jar from which the given extension was loaded, or null if this extension was added manually with addExtension().booleanisExtensionEnabled(String className) Reports whether the loaded extension with the given class name is enabled.booleanisExtensionLoaded(String className) Reports whether an extension with the given class name is currently loaded.booleanjarFileMeetsRequirements(File jarFile, AppExtensionInfo extInfo, String appName, String requiredVersion) Checks if the given jar file and extension info meet the given requirements (that is, that the application name and version requirements are met).loadExtensionFromJar(File jarFile, Class<T> extensionClass) Scans the given jar file looking for any classes that matches T.intScans the given directory looking for candidate jar files that contain an extension matching the given parameters.voidsetExtensionEnabled(String className, boolean isEnabled) Enables or disables the named extension - this involves sending an onActivate or onDeactivate message to the extension as needed.voidsetExtensionEnabled(String className, boolean isEnabled, boolean notify) Enables or disabled the named extension - if notify is true, will send the extension an onActivate or onDeactivate message as needed.sortExtensionJarSet(File directory, Set<File> jarSet) Given a Set of jar files in a given directory, this method looks for an optional load order control file and will attempt to obey any sorting directives it contains.intRemoves all extensions that were previously loaded in this ExtensionManager, and returns it to an empty state.booleanunloadExtension(String className) Unregisters the given extension, if it was registered.
-
Field Details
-
logger
-
-
Constructor Details
-
ExtensionManager
public ExtensionManager()
-
-
Method Details
-
getLoadedExtensionCount
public int getLoadedExtensionCount()Reports how many extensions have been loaded.- Returns:
- A count of loaded extensions (enabled or not).
-
isExtensionLoaded
Reports whether an extension with the given class name is currently loaded. Note you can also do getLoadedExtension(className) and check for null. Even if this method returns true, the extension might not be enabled! Use isExtensionEnabled() to determine if the extension is actually enabled.- Parameters:
className- The fully qualified name of the extension to look for.- Returns:
- True if the named extension has been loaded by this manager.
-
isExtensionEnabled
Reports whether the loaded extension with the given class name is enabled.- Parameters:
className- the fully qualified class name of the extension in question.- Returns:
- True if the extension is enabled, false if disabled or not found.
-
setExtensionEnabled
Enables or disables the named extension - this involves sending an onActivate or onDeactivate message to the extension as needed. If the given isEnabled value matches the existing value, no action is taken. If the given extension cannot be found, no action is taken.- Parameters:
className- The fully qualified class name of the extension in question.isEnabled- Whether to enable or disable the extension.
-
setExtensionEnabled
Enables or disabled the named extension - if notify is true, will send the extension an onActivate or onDeactivate message as needed. If the given isEnabled value matches the existing value, no action is taken. If the given extension cannot be found, no action is taken.- Parameters:
className- The fully qualified class name of the extension in question.isEnabled- Whether to enable or disable the extension.notify- Whether to send a message to the extension notifying them of the change.
-
getSourceJar
Returns the source jar from which the given extension was loaded, or null if this extension was added manually with addExtension().- Parameters:
className- the fully qualified class name of the extension in question.- Returns:
- The File representing the extensions source jar, or null if not found or built-in.
-
getLoadedExtension
Returns the extension instance for the given class name, if one exists.- Parameters:
className- the fully qualified class name of the extension in question.- Returns:
- The extension.
-
getAllLoadedExtensions
Returns a list of all loaded extensions - beware that this method will return extensions even if they are marked as disabled! If you only want to get the extensions that are currently enabled, use getEnabledLoadedExtensions() instead.- Returns:
- A List of zero or more extensions sorted by name.
-
getEnabledLoadedExtensions
Returns a list of all loaded extensions that are marked as enabled. This list is computed each time this method is called as extensions can be enabled and disabled at pretty much any point.- Returns:
- A List of zero or more enabled and loaded extensions.
-
getAllEnabledExtensionProperties
Invoke this to interrogate each enabled extension for their config properties, if any, and return them in a list.- Returns:
- The combined list of properties of all enabled extensions.
-
addExtension
Programmatically adds an extension to our list - this was originally intended for testing purposes, but might be useful as a way for applications to supply built-in extensions without having to package them in separate jar files with the distribution. Remember that extension load order matters! If more than one extension provides the same functionality, it's up to the application to decide which extension's version of it should be used. By convention, the first loaded extension that supplies a given piece of functionality is the one that will be used. You can use the ext-load-order.txt file in your extension jar directory to explicitly decide which extensions are loaded in which order.The extension will not receive an onActivate() notification from this method. Use activateAll() to start up extensions.
- Parameters:
extension- The extension class to be added.isEnabled- Whether to enable this extension immediately or not.
-
activateAll
public void activateAll()Sends an onActivate() message to each enabled extension, to let them know that we're starting up - use deactivateAll() to signal shutdown. -
deactivateAll
public void deactivateAll()Sends an onDeactivate() message to each enabled extension, to let them know that we're shutting down - use activateAll() to signal startup. -
unloadAllExtensions
public int unloadAllExtensions()Removes all extensions that were previously loaded in this ExtensionManager, and returns it to an empty state. Client applications should use this with caution - if you have queried for configuration properties from all extensions before invoking this, then you should once again invoke getAllEnabledExtensionProperties() because the list of properties will likely decrease as extensions are unloaded, and your UI should remove the properties that no longer exist.- Returns:
- The count of extensions that were actually removed as a result of this call.
-
unloadExtension
Unregisters the given extension, if it was registered. The extension will receive a deactivate() message before it is unloaded, if it was enabled. Client applications should use this with caution - if you have queried for configuration properties from all extensions before invoking this, then you should once again invoke getAllEnabledExtensionProperties() because the list of properties will likely decrease as extensions are unloaded, and your UI should remove the properties that no longer exist.- Parameters:
className- The class of the extension to be unregistered.- Returns:
- true if an extension was actually removed as a result of this call.
-
loadExtensions
public int loadExtensions(File directory, Class<T> extClass, String appName, String requiredVersion) Scans the given directory looking for candidate jar files that contain an extension matching the given parameters. For each jar that is found, an attempt will be made to load the extension class out of that jar file. All successfully loaded extension classes will then be loaded into this ExtensionManager.Note that this is a shorthand way of doing this more manually (or jar by jar) via the findCandidateExtensionJars, extractExtInfo, and jarFileMeetsRequirements methods. Generally, this is the better entry point, but if you have a specific jar file that you want to scan and load from, those other methods can be used instead.
- Parameters:
directory- The directory to scan.extClass- The implementation class to look for.appName- The application name to match against.requiredVersion- The required application version that the extension must target.- Returns:
- The count of extensions that were loaded by this operation.
-
findCandidateExtensionJars
public Map<File,AppExtensionInfo> findCandidateExtensionJars(File directory, String appName, String requiredVersion) Scans the given directory looking for Jar files that contain an extInfo.json file, and if one is found, will check its parameters against the given appName and requiredVersion to make sure the extension would work for that application. The return is a Map of File to AppExtensionInfo, which can then be loaded via one of the loadExtension methods. Note that this method does not actually try to load the extension, it simply scans to find which jar file would be good candidates for loading. This can therefore be used for autodiscovery of extension jars in a given directory safely, without actually loading them. Note that both appName and requiredVersion are optional (you can pass null to disable those checks), but the result may be jar files that contain extensions for the wrong app, or for the wrong version of the app, or both.- Parameters:
directory- The directory to scan (will be scanned recursively).appName- The application name to check for, or null to skip this check.requiredVersion- The required app version, or null to skip this check.- Returns:
- A Map of jar files to AppExtensionInfo objects.
-
jarFileMeetsRequirements
public boolean jarFileMeetsRequirements(File jarFile, AppExtensionInfo extInfo, String appName, String requiredVersion) Checks if the given jar file and extension info meet the given requirements (that is, that the application name and version requirements are met). This does not guarantee that an extension can be successfully loaded out of the given jar file, but it is a pretty good indicator.- Parameters:
jarFile- The jar file in question.extInfo- The extension info that was extracted from that jar via extractExtInfoappName- The name of the application to check for.requiredVersion- The app version that the extension must target.- Returns:
- true if the jar file looks good, false otherwise.
-
loadExtensionFromJar
Scans the given jar file looking for any classes that matches T. The first matching class found will be loaded as an extension of type T and returned. Multiple extension implementations in the same jar file is therefore not supported - package them into separate jar files instead.- Parameters:
jarFile- The jar file to scan.extensionClass- The implementing class to look for.- Returns:
- An implementation of T if one could be found and loaded, otherwise null.
-
extractExtInfo
Invoked internally to look for an extInfo.json file inside the given jar file and attempt to parse an AppExtensionInfo object out of it. Upon success, the newly created AppExtensionInfo is returned. If anything goes wrong, the error is logged and null is returned.Packaging an extInfo.json file into your extension jar
Your jar file should contain an extInfo.json file somewhere in its resources. We'll scan every entry in the jar file looking for it, so the exact location doesn't matter, but a good convention is: resources/fully/qualified/main/package/extInfo.jsonYou can easily generate an extInfo.json by populating an AppExtensionInfo object and invoking toJson() on it.
- Parameters:
jarFile- The jar file in question.- Returns:
- An AppExtensionInfo, or null.
-
sortExtensionJarSet
Given a Set of jar files in a given directory, this method looks for an optional load order control file and will attempt to obey any sorting directives it contains. If the file is missing or incomplete, the input set will be sorted by filename. If the load order file mentions any jar files that don't exist in that directory, those directives are ignored.Formatting the load order file
Blank lines and lines starting with a hash character are ignored. All other lines in the file are assumed to be the name (without path) of a single jar file. The order in which those jars are listed in this file is the order that the extension jars will be loaded.SPECIAL NOTE: application built-in extensions are generally loaded first by convention! That can't be overridden in the load order control file.
- Parameters:
directory- The directory to scanjarSet- The Set of jar files to consider within that directory- Returns:
- A sorted List of jar files. This list.size() will always match the input Set's size.
-
getAllLoadedExtensionWrappers
Invoked internally to return a list of all loaded extension wrappers, sorted by the extension name.- Returns:
- A List of ExtensionWrappers, sorted by extension name;
-