LocalCachingContextClassLoaderFactory implements Accumulo's ContextClassLoaderFactory SPI. Given a context parameter, supplied as a String containing a URL to a remote context definition file, it will produce and return ClassLoader instances to load classes and resources for a Java application, based on the list of resource URLs contained in the remote context definition. It will also monitor the URL to the context definition file for any changes, at the monitoring interval specified in the context definition file.
This factory creates ClassLoader instances that point to locally cached copies of remote resource files. In this way, this factory allows placing common resources in a remote location for use across many hosts, but without many of the problems that can occur when loading resources from a remote location.
This factory uses a storage cache in the local filesystem for any files it downloads from a remote URL.
To use this factory, one must store resource files in a location that can be specified by a supported URL, and then must create a JSON-formatted context definition file that contains a monitoring interval (in seconds, greater than 0), and a list of resource URLs along with a checksum for each resource file. This context definition file must then be stored somewhere where this factory can download it, and use the URL to that context definition file as the context parameter for this factory's getClassLoader(String context) method.
This factory can handle context and resource URLs that use the file, hdfs, http, or https URL scheme.
Here is an example context definition file:
{ "monitorIntervalSeconds": 5, "resources": [ { "location": "file:/home/user/ClassLoaderTestA/TestA.jar", "checksum": "a10883244d70d971ec25cbfa69b6f08f" }, { "location": "hdfs://localhost:8020/contextB/TestB.jar", "checksum": "a02a3b7026528156fb782dcdecaaa097" }, { "location": "http://localhost:80/TestC.jar", "checksum": "f464e66f6d07a41c656e8f4679509215" } ] }
When this factory receives a request for a ClassLoader for a given URL, it downloads a copy of the context definition file and parses it. If it has recently acquired that context definition file, based on the monitoring interval from a previous retrieval, it can skip this step and use the definition from the earlier retrieval, which is kept up-to-date by the background monitoring process that started when it was previously retrieved. Once it has the context definition, it then returns a ClassLoader instance containing the resources defined in that context definition file, first downloading any missing resources and verifying them using the checksums in the context definition file.
ClassLoader instances are stored in a de-deduplicating cache in memory with a minimum lifetime of 24 hours. So, no two instances will ever exist in a process for the same context definition.
If this context definition had not previously been downloaded, a background monitoring task is set up to ensure the URL is watched for any changes to the context definition. This monitoring continues for as long as there exists ClassLoader instances in the system that were constructed from the definition file at that URL (at least 24 hours, since that is the minimum time they will exist in the de-duplicating cache).
The local storage cache location is configured by the user by setting the Accumulo property named general.custom.classloader.lcc.cache.dir to a directory on the local filesystem. This location may be specified as an absolute path or as a URL representing an absolute path with the file scheme.
The selected location should be a persistent location with plenty of space to store downloaded resources (usually jar files), and should be writable by all the processes which use this factory to share the same resources.
Resources downloaded to this cache may be used by multiple contexts, threads, and processes, so be very careful when removing old contents to ensure that they are no longer needed. If a resource file is deleted from the local storage cache while a ClassLoader exists that references it, that ClassLoader may, and probably will, stop working correctly. Similarly, files that have been downloaded should not be modified, because checksums are only verified on first download, and any modification will likely cause unexpected behavior.
Users may take advantage of the ContextDefinition.create(int,URL[]) method to construct a ContextDefinition object. This will calculate the checksums of the classpath elements. ContextDefinition.toJson() can be used to serialize the ContextDefinition to a String to store in a file.
This factory uses a background thread to fetch the context definition file at its initial URL using the interval specified in the definition file the last time it was retrieved. The definition file at a watched URL can be replaced at any time with any changes, including to changes to the monitor interval and resources. When the context definition is next retrieved, the new context definition file will be used as though it were an entirely new context at that URL. The next retrieval will occur after the monitor interval read from the most recent retrieval elapses. Changes to the context resources in any way will result in those new resources being downloaded, verified, and a new ClassLoader instance created and ready to be returned the next time getClassLoader(String context) is called with that context URL.
Note: if the contents of a context definition file change in only inconsequential ways, such as JSON formatting changes, then those changes will not trigger any new downloads or ClassLoader staging. The is because the context definition JSON files are normalized prior to computing their checksum to determine if any changes have occurred.
If there is an exception in creating the initial ClassLoader, then a ContextClassLoaderException is thrown. If there is an exception when updating the classloader, then the exception is logged and the classloader is not updated. Calls to getClassLoader(String context) will return the most recent classloader with valid contents. If the checksum of a downloaded resource does not match the checksum in the context definition file, then the downloaded version of the file is deleted from the context cache directory so that it can be retried at the next interval.
The property general.custom.classloader.lcc.update.grace.minutes determines how long the update process continues to return the most recent valid classloader when an exception occurs in the background update thread. A zero value (default) will cause the most recent valid classloader to be returned. Otherwise, if a non-zero number is configured, then monitoring will stop after the update has failed for that number of minutes. Once monitoring has stopped, any subsequent calls to getClassLoader(String context) will behave as it would during an initial request, throwing a ContextClassLoaderException if the context definition cannot be retrieved or a ClassLoader cannot be constructed at that time.
Because the cache directory is shared among multiple processes, and one process can't know what the other processes are doing, this class cannot clean up the shared cache directory of unused resources. It is left to the user to remove unused files from the cache. While the context definition JSON files are always safe to delete, it is not recommended to do so for any that are still in use, because they can be useful for troubleshooting.
IMPORTANT: as mentioned earlier, it is not safe to delete resource files that are still referenced by any ClassLoader instances. Each ClassLoader instance assumes that the locally cached resources exist and can be read. They will not attempt to download any files. Downloading and verifying files only occurs when ClassLoader instances are initially created for a context definition.
To use this with Accumulo:
general.context.class.loader.factory=org.apache.accumulo.classloader.lcc.LocalCachingContextClassLoaderFactorygeneral.custom.classloader.lcc.cache.dir=file://path/to/some/directorytable.class.loader.context=(file|hdfs|http|https)://path/to/context/definition.json