Below is a cache example that uses the popular Java Ehcache system.
First, generate or obtain an Ehcache manager configuration file. This is an example that creates a cache with off heap options and has the alias leadtools:
Content of ehcache.xml
<configxmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'xmlns='http://www.ehcache.org/v3'xsi:schemaLocation="http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core.xsd"><cache alias="leadtools"><key-type>java.lang.String</key-type><value-type>java.io.Serializable</value-type><resources><heap unit="MB">100</heap><offheap unit="GB">1</offheap></resources></cache></config>
Next, add the following LEADTOOLS cache configuration XML file to the Document service. This XML file references the class implementing ehCache described below, the cache manager file and alias:
Content of lead-ehcache.xml
<?xml version="1.0" encoding="utf-8"?><leadtools_cache><cache><type>com.yourapp.EhcacheObjectCache</type><values><value key="manager-config-file" value="ehcache.xml" /><value key="cache-alias" value="leadtools" /> <!-- must be defined in above cache manager config file --></values></cache></leadtools_cache>
Replace the code inside ServiceHelper.CreateCache with initialization of the Ehcache object as shown in the example:
// The path to lead_ehcache_config.xmlString config = "lead_ehcache_config.xml";try (FileInputStream fis = new FileInputStream(config)) {try (LeadDynamicStream lds = new LeadDynamicStream(fis, true)) {// Create itObjectCache cache = ObjectCache.createFromConfigurations(lds, null);// Use itServiceHelper._cache = cache;}}
Finally, this is implementation of EhcacheObjectCache:
package com.yourapp;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.File;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;import java.net.MalformedURLException;import java.net.URI;import java.net.URL;import java.util.EnumSet;import java.util.HashSet;import java.util.Iterator;import java.util.Map;import java.util.Set;import org.ehcache.Cache;import org.ehcache.CacheManager;import org.ehcache.config.builders.CacheManagerBuilder;import org.ehcache.xml.XmlConfiguration;import leadtools.InvalidOperationException;import leadtools.RasterException;import leadtools.caching.CacheItem;import leadtools.caching.CacheItemExpiredEvent;import leadtools.caching.CacheItemExpiringEvent;import leadtools.caching.CacheItemPolicy;import leadtools.caching.CacheSerializationMode;import leadtools.caching.CacheStatistics;import leadtools.caching.DefaultCacheCapabilities;import leadtools.caching.EnumerateCacheEntriesCallback;import leadtools.caching.ObjectCache;/*Implementation of leadtools.caching.ObjectCache that wraps an org.ehcache.Cache object1. Create or load an instance Cache.2. Create an instance of EhcacheObjectCache to wrap it3. Pass it to LEADTOOLS Document toolkit that expect a CacheObject. For instance, DocumentFactory.loadFromUrl or DocumentFactory.loadFromCache. Then everythingshould work as expected4. To delete a document from the cache, use DocumentFactory.deleteFromCacheThis implementation does not support individual expiration policies for each item since this is not supported by Ehcache. Instead, it will use the policiesalready setup in the original Ehcache object passed.The document toolkit will call the following methods of ObjectCache:- getDefaultCacheCapabilities to get the kind of support of this cache. We support minimal requirement of get/set only (no regions, external URLs, diskaccess nor auto-serialization).- addOrGetExisting: To add items to the cache using the following parameters:item.regionName: Will always be the document ID (leadtools.documents.Document.getDocumentId)item.keyName: Unique identified for this item, such as page1_image or document_metadata.The implementation uses regionName + "_" + keyName to create a unique identifier in the cache since true grouping is not supported by Ehcache.item.Value: The value to store, this could be:- Array of bytes: In case of original document data and annotation data- Strings: In case of raster codecs options, meta data, etc.- RasterImage and ISvgDocument objects: If DocumentCacheOptions.PAGE_IMAGE, PAGE_SVG, PAGE_SVG_BACK_IMAGE or PAGE_THUMBNAIL_IMAGE was used to cache images. Ifthis occurs, it is recommended that the implementer calls the static tryDispose method from a CacheEventListender when items are removed, evicted, expired orupdates. Refer to the example code for more information.- getCacheItem: to get items from the cache. Refer to the parameters above.- remove: to remove an item from the cache while returning the original value.- deleteItem: to delete an item from the cache quickly.- deleteAll: to delete multiple items from the cache quickly. This is called when the document is removed from the cache usingDocument.setAutoDeleteFromCache(true) or DocumentFactory.deleteFromCache.*/public class EhcacheObjectCache extends ObjectCache {// Configuration options for this classpublic static class Configuration {// For future use}// The Ehcache object we are wrappingprivate Cache<String, Serializable> _cache;public Cache<String, Serializable> cache() {return _cache;}// No param constructor required for super.createFromConfigurations(ILeadStream)// where this object is instantiated via reflectionpublic EhcacheObjectCache() {super();}public EhcacheObjectCache(Cache<String, Serializable> cache) {super();if (cache == null) {throw new IllegalArgumentException("cache must not be null");}init(cache, null, null, null);}public EhcacheObjectCache(Cache<String, Serializable> cache, EhcacheObjectCache.Configuration config) {super();if (cache == null) {throw new IllegalArgumentException("cache must not be null");}init(cache, null, null, config);}public EhcacheObjectCache(String cacheManagerConfigFile, String cacheAlias) {super();if (cacheManagerConfigFile == null || cacheAlias == null) {throw new IllegalArgumentException("cacheManagerConfigFile and cacheAlias must not be null");}init(null, cacheManagerConfigFile, cacheAlias, null);}public EhcacheObjectCache(String cacheManagerConfigFile, String cacheAlias, EhcacheObjectCache.Configuration config) {super();if (cacheManagerConfigFile == null || cacheAlias == null) {throw new IllegalArgumentException("cacheManagerConfigFile and cacheAlias must not be null");}init(null, cacheManagerConfigFile, cacheAlias, config);}public void init(Cache<String, Serializable> cache, String managerConfigFile, String cacheAlias, EhcacheObjectCache.Configuration config) {if(cache != null) {this._cache = cache;}else {Cache<String, Serializable> createdCache = cacheFromManagerConfig(managerConfigFile, cacheAlias);if(createdCache != null) {init(createdCache, null, null, config);return;}}}// Helper method to resolve region and key pair. The document will refer to each item by a region (the document ID, not unique in the cache) and a key// (the value itself, unique for the document ID). Ehcache does not support that, so simply concatenate both values to generate a unique keyprivate static String resolveKey(String key, String region) {if (region == null || region.length() == 0) {// We do not support thisthrow new UnsupportedOperationException("Cache region must be provided");}return region + "_" + key;}// Not used in this implementation@Overridepublic CacheItemExpiringEvent cacheItemExpiring() {return null;}// Not used in this implementation@Overridepublic CacheItemExpiredEvent cacheItemExpired() {return null;}@Overridepublic String getName() {return "Ehcache ObjectCache";}// We only support binary serialization@Overridepublic CacheSerializationMode getPolicySerializationMode() {return CacheSerializationMode.BINARY;}@Overridepublic void setPolicySerializationMode(CacheSerializationMode value) {// This is never called from the document toolkit}@Overridepublic CacheSerializationMode getDataSerializationMode() {return CacheSerializationMode.BINARY;}@Overridepublic void setDataSerializationMode(CacheSerializationMode value) {// This is never called from the document toolkit}@Overridepublic boolean contains(String key, String regionName) {if (key == null)throw new IllegalArgumentException("Key cannot be null");return _cache.containsKey(resolveKey(key, regionName));}@Overridepublic <T> T get(String key, Class<?> classOfT) {// This is never called from the document toolkitthrow new UnsupportedOperationException();}@Overridepublic <T> void set(String key, T value, Class<?> classOfT) {// This is never called from the document toolkitthrow new UnsupportedOperationException();}@Overridepublic void enumerateRegions(EnumerateCacheEntriesCallback callback) {// This is never called from the document toolkitthrow new UnsupportedOperationException();}@Overridepublic void enumerateKeys(String region, EnumerateCacheEntriesCallback callback) {// This is never called from the document toolkitthrow new UnsupportedOperationException();}@Overridepublic Map<String, Object> getValues(Iterator<String> keys, String regionName) {// This is never called from the document toolkitthrow new UnsupportedOperationException();}private static boolean isAutoSerializable(Class<?> classOfT) {String classOfTName = classOfT.getCanonicalName();if (classOfTName.equals("java.lang.Long") ||classOfTName.equals("java.lang.Integer") ||classOfTName.equals("java.lang.Float") ||classOfTName.equals("java.lang.Double") ||classOfTName.equals("java.lang.Character") ||classOfTName.equals("java.lang.String") ||classOfTName.equals("byte[]"))return true;elsereturn false;}private <T> void putValue(String resolvedKey, T value, Class<?> classOfT) throws IOException {if (!isAutoSerializable(classOfT)) {try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {try (ObjectOutputStream out = new ObjectOutputStream(bos)) {out.writeObject(value);byte[] data = bos.toByteArray();_cache.put(resolvedKey, data);}}} else {_cache.put(resolvedKey, (Serializable)value);}}@SuppressWarnings("unchecked")private <T> T getValue(String resolvedKey, Class<?> classOfT) throws ClassNotFoundException, IOException {if (!isAutoSerializable(classOfT)) {byte[] data = (byte[])_cache.get(resolvedKey);if (data != null) {try (ByteArrayInputStream bis = new ByteArrayInputStream(data)) {try (ObjectInputStream in = new ObjectInputStream(bis)) {T value = (T)in.readObject();return value;}}}} else {Object value = _cache.get(resolvedKey);if (value != null) {return (T)value;}}return null;}@Overridepublic <T> CacheItem<T> addOrGetExisting(CacheItem<T> item, Class<?> classOfT, CacheItemPolicy policy) {if (item == null) {throw new IllegalArgumentException("item cannot be null");}// Get the unique key// Get the current item (if exists)// Put the new item// Return the old item// Note: policy is not usedString resolvedKey = resolveKey(item.getKey(), item.getRegionName());CacheItem<T> oldItem = null;if (_cache.containsKey(resolvedKey)) {try {oldItem = getCacheItem(item.getKey(), classOfT, item.getRegionName());} catch (Exception ex) {oldItem = null;}}if(item.getValue() == null)throw new RuntimeException("CacheItem value cannot be null");try {putValue(resolvedKey, item.getValue(), classOfT);} catch (IOException e) {throw RasterException.fromThrowable(e);}return oldItem;}@Overridepublic <T> CacheItem<T> getCacheItem(String key, Class<?> classOfT, String regionName) {// Get the unique key// Get the itemString resolvedKey = resolveKey(key, regionName);T value;try {value = getValue(resolvedKey, classOfT);} catch (ClassNotFoundException | IOException e) {throw RasterException.fromThrowable(e);}if (value != null) {return new CacheItem<T>(key, value, regionName);}return null;}@Overridepublic <T> T remove(String key, Class<?> classOfT, String regionName) {// Get the unique key// Get the old value// Remove the item// Return the old valueT value = null;String resolvedKey = resolveKey(key, regionName);if (_cache.containsKey(resolvedKey)) {try {value = getValue(resolvedKey, classOfT);} catch (ClassNotFoundException | IOException e) {throw RasterException.fromThrowable(e);}_cache.remove(resolvedKey);}return value;}@Overridepublic void deleteItem(String key, String regionName) {// Get the unique key// Remove the itemString resolvedKey = resolveKey(key, regionName);if (_cache.containsKey(resolvedKey)) {_cache.remove(resolvedKey);}}@Overridepublic long getCount(String regionName) {// This is never called from the document toolkitthrow new UnsupportedOperationException();}@Overridepublic EnumSet<DefaultCacheCapabilities> getDefaultCacheCapabilities() {EnumSet<DefaultCacheCapabilities> caps = EnumSet.of(DefaultCacheCapabilities.NONE);caps.add(DefaultCacheCapabilities.SERIALIZATION);return caps;}@Overridepublic URI getItemVirtualDirectoryUrl(String key, String regionName) {throw new UnsupportedOperationException();}@Overridepublic CacheStatistics getStatistics(String keyName, String regionName) {// This is never called from the document toolkitthrow new UnsupportedOperationException();}@Overridepublic CacheStatistics getStatistics() {// This is never called from the document toolkitthrow new UnsupportedOperationException();}@Overridepublic void deleteRegion(String regionName) {throw new UnsupportedOperationException("Method not supported. Use deleteAll");// One cache per document implementation. Try to delete _cache from the manage if not needed}@Overridepublic URI getItemExternalResource(String key, String regionName, boolean readWrite) {/// This is never called from the document toolkit since we do not support DefaultCacheCapabilities.EXTERNAL_RESOURCESthrow new UnsupportedOperationException();}@Overridepublic void removeItemExternalResource(String key, String regionName) {/// This is never called from the document toolkit since we do not support DefaultCacheCapabilities.EXTERNAL_RESOURCESthrow new UnsupportedOperationException();}@Overridepublic URI beginAddExternalResource(String key, String regionName, boolean readWrite) {/// This is never called from the document toolkit since we do not support DefaultCacheCapabilities.EXTERNAL_RESOURCESthrow new UnsupportedOperationException();}@Overridepublic <T> void endAddExternalResource(boolean commit, String key, T value, Class<?> classOfT, CacheItemPolicy policy, String regionName) {/// This is never called from the document toolkit since we do not support DefaultCacheCapabilities.EXTERNAL_RESOURCESthrow new UnsupportedOperationException();}@Overridepublic void updatePolicy(String key, CacheItemPolicy policy, String regionName) {// Method not supported. Policies are set on the Cache object passed to the constructor.// This will be called from the document toolkit. But we can safely ignore it and rely on the// expiration policies always set up in the Ehcache object from outside.}@Overridepublic <T> boolean updateCacheItem(CacheItem<T> item, Class<?> classOfT) {// Get the unique key// If item found, update it// return statusString resolvedKey = resolveKey(item.getKey(), item.getRegionName());if (_cache.containsKey(resolvedKey)) {try {putValue(resolvedKey, item.getValue(), classOfT);} catch (IOException e) {throw RasterException.fromThrowable(e);}return true;} else {return false;}}@Overridepublic void deleteAll(String regionName, Set<String> keys) {if (keys == null) {return;}// Remove all the keys// Note that the document toolkit will call this method with keys for items that may not exist// in the cache, Ehcache does not have a problem with that so just call removeAll instead// of trying to iterate through the keys to check if they exist first.// Of course, convert to our key format firstSet<String> resolvedKeys = new HashSet<String>();for (String key: keys) {resolvedKeys.add(resolveKey(key, regionName));}_cache.removeAll(resolvedKeys);}private Cache<String, Serializable> cacheFromManagerConfig(String managerConfigFile, String cacheAlias) {if (managerConfigFile != null) {URL url = Thread.currentThread().getContextClassLoader().getResource(managerConfigFile);if(url == null) {//Loading resource failed. Try absolute path.File configFile = new File(managerConfigFile);if(configFile.exists()) {try {url = configFile.toURI().toURL();}catch(MalformedURLException ex) {throw new InvalidOperationException("CacheManager config file could not be found");}}}this.setManagerConfigFile(managerConfigFile);XmlConfiguration xmlConfig = new XmlConfiguration(url);_cacheManager = CacheManagerBuilder.newCacheManager(xmlConfig);_cacheManager.init();if(cacheAlias != null) {Cache<String, Serializable> cache = _cacheManager.getCache(cacheAlias, String.class, Serializable.class);this.setCacheAlias(cacheAlias);return cache;}else {throw new InvalidOperationException("Ehcache cache alias must not be null");}}else {throw new InvalidOperationException("Ehcache CacheManager config file must not be null");}}@Overrideprotected void loadConfigurationValues(Map<String, String> values) {super.loadConfigurationValues(values);String configFile = values.get("manager-config-file");String cacheAlias = values.get("cache-alias");if(configFile == null || cacheAlias == null) {throw new InvalidOperationException("Ehcache CacheManager config file and Ehcache cache alias must not be null");}EhcacheObjectCache.Configuration cfg = new EhcacheObjectCache.Configuration();Cache<String, Serializable> cache = cacheFromManagerConfig(configFile, cacheAlias);init(cache, null, null, cfg);}@Overrideprotected void saveConfigurationValues(Map<String, String> values){super.saveConfigurationValues(values);if(this.getManagerConfigFile() != null) {values.put("manager-config-file", this.getManagerConfigFile());}if(this.getCacheAlias() != null) {values.put("cache-alias", this.getCacheAlias());}}private String _configFile;// Can be null if this object is initiated with a Cache reference instead of// a CacheManager configuration filepublic String getManagerConfigFile() {return _configFile;}void setManagerConfigFile(String configFile) {this._configFile = configFile;}private CacheManager _cacheManager;// Can be null if this object is initiated with a Cache reference instead of// a CacheManager configuration filepublic CacheManager getManager() {return _cacheManager;}void setManager(CacheManager manager) {this._cacheManager = manager;}private String _cacheAlias;// Can be null if this object is initiated with a Cache reference instead of// a CacheManager configuration filepublic String getCacheAlias() {return this._cacheAlias;}void setCacheAlias(String cacheAlias) {this._cacheAlias = cacheAlias;}}