Skip to content

Commit

Permalink
[cache] Reimplementation to use the new core caches (#191)
Browse files Browse the repository at this point in the history
Fixes #184.

* [cache] Change implementation to the core caches
... while keeping backward compatibility with older openHAB versions by providing a legacy mode using the addon provided „sharedcache“.
* [cache] Improve logging
* [test] Test JSCache instead of exported methods
* README: Explain private & shared caches
* [cache] Update type definitions
* Update CHANGELOG
* README: Mention automatic cancellation of timers in cache
* [cache] Add timer cancellation note to JSDoc

Signed-off-by: Florian Hotze <florianh_dev@icloud.com>
  • Loading branch information
florian-h05 authored Dec 11, 2022
1 parent 21edcde commit fd61b53
Show file tree
Hide file tree
Showing 7 changed files with 278 additions and 189 deletions.
18 changes: 10 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,21 @@

## to be released

| Type | Namespace | Description | Reference | Breaking |
|-------------|-----------|---------------------------------------------------------------------|--------------------------------------------------------|----------|
| Type | Namespace | Description | Reference | Breaking |
|-------------|-----------|---------------------------------------------|---------------------------------------------------------------------------------------------------------|----------|
| Enhancement | `utils` | Support all data types in `dumpObject` | Commit [144e6a9](https://github.com/openhab/openhab-js/commit/144e6a9c58982f45a07bf704ac0bd18fa6c3a54d) | No |
| Enhancement | `cache` | Reimplementation to use the new core caches | [#191](https://github.com/openhab/openhab-js/pull/191) | No |

Also see the [Release Milestone](https://github.com/openhab/openhab-js/milestone/9).

## 3.0.0

| Type | Namespace | Description | Reference | Breaking |
|-------------|-----------|---------------------------------------------------------------------|--------------------------------------------------------|----------|
| Enhancement | `items` | ItemHistory: Change return types of min/max between/since to number | [#175](https://github.com/openhab/openhab-js/pull/175) | **Yes** |
| Cleanup | `rules` | Remove unused rule providers | [#183](https://github.com/openhab/openhab-js/pull/183) | **Yes** |
| Enhancement | `actions` | Add Transformation actions as a class with arg type checking | [#180](https://github.com/openhab/openhab-js/pull/180) | No |
| Cleanup | | Remove unused & non-working providers | Commit 83dac55d | No |
| Type | Namespace | Description | Reference | Breaking |
|-------------|-----------|---------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------|----------|
| Enhancement | `items` | ItemHistory: Change return types of min/max between/since to number | [#175](https://github.com/openhab/openhab-js/pull/175) | **Yes** |
| Cleanup | `rules` | Remove unused rule providers | [#183](https://github.com/openhab/openhab-js/pull/183) | **Yes** |
| Enhancement | `actions` | Add Transformation actions as a class with arg type checking | [#180](https://github.com/openhab/openhab-js/pull/180) | No |
| Cleanup | | Remove unused & non-working providers | Commit [83dac55d](https://github.com/openhab/openhab-js/commit/83dac55d67099494661d9bbee9cd0a58cca3e2b0) | No |

Also see the [Release Milestone](https://github.com/openhab/openhab-js/milestone/8).

Expand Down
40 changes: 27 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -692,34 +692,48 @@ Replace `<message>` with the notification text.
### Cache
The cache namespace provides a default cache that can be used to set and retrieve objects that will be persisted between reloads of scripts.
The cache namespace provides both a private and a shared cache that can be used to set and retrieve objects that will be persisted between subsequent runs of the same or between scripts.
The private cache can only be accessed by the same script and is cleared when the script is unloaded.
You can use it to e.g. store timers or counters between subsequent runs of that script.
When a script is unloaded and its cache is cleared, all timers (see [ScriptExecution Actions](#scriptexecution-actions)) stored in its private cache are cancelled.
The shared cache is shared across all rules and scripts, it can therefore be accessed from any automation language.
The access to every key is tracked and the key is removed when all scripts that ever accessed that key are unloaded.
If that key stored a timer, the timer is cancelled.
See [openhab-js : cache](https://openhab.github.io/openhab-js/cache.html) for full API documentation.
- cache : <code>object</code>
- .get(key, defaultSupplier) ⇒ <code>Object | null</code>
- .put(key, value) ⇒ <code>Previous Object | null</code>
- .remove(key) ⇒ <code>Previous Object | null</code>
- .exists(key) ⇒ <code>boolean</code>
- .private
- .get(key, defaultSupplier) ⇒ <code>Object | null</code>
- .put(key, value) ⇒ <code>Previous Object | null</code>
- .remove(key) ⇒ <code>Previous Object | null</code>
- .exists(key) ⇒ <code>boolean</code>
- .shared
- .get(key, defaultSupplier) ⇒ <code>Object | null</code>
- .put(key, value) ⇒ <code>Previous Object | null</code>
- .remove(key) ⇒ <code>Previous Object | null</code>
- .exists(key) ⇒ <code>boolean</code>
The `defaultSupplier` provided function will return a default value if a specified key is not already associated with a value.
**Example** *(Get a previously set value with a default value (times &#x3D; 0))*
**Example** *(Get a previously set value with a default value (times = 0))*
```js
var counter = cache.get("counter", () => ({ "times": 0 }));
console.log("Count",counter.times++);
var counter = cache.private.get('counter', () => ({ 'times': 0 }));
console.log('Count', counter.times++);
```
**Example** *(Get a previously set object)*
```js
var counter = cache.get("counter");
if(counter == null){
counter = {times: 0};
cache.put("counter", counter);
var counter = cache.private.get('counter');
if (counter === null) {
counter = { times: 0 };
cache.private.put('counter', counter);
}
console.log("Count",counter.times++);
console.log('Count', counter.times++);
```
### Log
Expand Down
169 changes: 106 additions & 63 deletions cache.js
Original file line number Diff line number Diff line change
@@ -1,77 +1,120 @@
/**
* Shared cache namespace.
* This namespace provides a default cache that can be used to set and retrieve objects that will be persisted between reloads of scripts.
*
* Cache namespace.
* This namespace provides caches that can be used to set and retrieve objects that will be persisted between reloads of scripts.
* @namespace cache
*/

const cache = require('@runtime').sharedcache;
const log = require('./log')('cache');
const { privateCache, sharedCache } = require('@runtime/cache'); // The new cache from core
const addonSharedCache = require('@runtime').sharedcache; // The old cache from the adddon

/**
* Returns the value to which the specified key is mapped
*
* @example <caption>Get a previously set value with a default value (times = 0)</caption>
* let counter = cache.get("counter", () => ({ "times": 0 }));
* console.log("Count",counter.times++);
*
* @example <caption>Get a previously set object</caption>
* let counter = cache.get("counter");
* if(counter == null){
* counter = {times: 0};
* cache.put("counter", counter);
* }
* console.log("Count",counter.times++);
*
* @memberof cache
* @param {string} key the key whose associated value is to be returned
* @param {function} [defaultSupplier] if the specified key is not already associated with a value, this function will return a default value
* @returns {(*|null)} the current object for the supplied key, a default value if defaultSupplier is provided, or null
*/
const get = function (key, defaultSupplier) {
if (typeof defaultSupplier === 'function') {
return cache.get(key, defaultSupplier);
} else {
return cache.get(key);
}
};
const coreCacheAvail = Java.isJavaObject(privateCache) && Java.isJavaObject(sharedCache);

/**
* Associates the specified value with the specified key
*
* @memberof cache
* @param {string} key key with which the specified value is to be associated
* @param {*} value value to be associated with the specified key
* @returns {(*|null)} the previous value associated with null, or null if there was no mapping for key
*/
const put = function (key, value) {
return cache.put(key, value);
const logDeprecationWarning = (funcName) => {
console.warn(`"cache.${funcName}" has been deprecated and will be removed in a future release. Use "cache.private.${funcName}" or "cache.shared.${funcName}" instead. Visit the JavaScript Scripting Automation addon docs for more information about the cache.`);
};

/**
* Removes the mapping for a key from this map if it is present
*
* @memberof cache
* @param {string} key key whose mapping is to be removed from the map
* @returns {(*|null)} the previous value associated with key or null if there was no mapping for key
* The {@link JSCache} can be used by to share information between subsequent runs of the same script or between scripts (depending on implementation).
*/
const remove = function (key) {
return cache.remove(key);
};
class JSCache {
/**
* @param {*} valueCacheImpl an implementation of the Java {@link https://github.com/openhab/openhab-core/blob/main/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/shared/ValueCache.java ValueCache} interface
* @param {boolean} [deprecated]
* @hideconstructor
*/
constructor (valueCacheImpl, deprecated = false) {
this._valueCache = valueCacheImpl;
this._deprecated = deprecated;
}

/**
* Checks the mapping for a key from this map.
*
* @memberof cache
* @param {string} key key whose mapping is to be checked in the map
* @returns {boolean} whether the key has a mapping
*/
const exists = function (key) {
return get(key) !== null;
};
/**
* Returns the value to which the specified key is mapped.
*
* @param {string} key the key whose associated value is to be returned
* @param {function} [defaultSupplier] if the specified key is not already associated with a value, this function will return a default value
* @returns {*|null} the current object for the supplied key, a default value if defaultSupplier is provided, or null
*/
get (key, defaultSupplier) {
if (this._deprecated === true) logDeprecationWarning('get');
if (typeof defaultSupplier === 'function') {
return this._valueCache.get(key, defaultSupplier);
} else {
return this._valueCache.get(key);
}
}

/**
* Associates the specified value with the specified key.
*
* @param {string} key key with which the specified value is to be associated
* @param {*} value value to be associated with the specified key
* @returns {*|null} the previous value associated with the key, or null if there was no mapping for key
*/
put (key, value) {
if (this._deprecated === true) logDeprecationWarning('put');
return this._valueCache.put(key, value);
}

/**
* Removes the mapping for a key from this map if it is present.
*
* @param {string} key key whose mapping is to be removed from the cache
* @returns {*|null} the previous value associated with the key or null if there was no mapping for key
*/
remove (key) {
if (this._deprecated === true) logDeprecationWarning('remove');
return this._valueCache.remove(key);
}

/**
* Checks the mapping for a key from this map.
*
* @param {string} key key whose mapping is to be checked in the map
* @returns {boolean} whether the key has a mapping
*/
exists (key) {
if (this._deprecated === true) logDeprecationWarning('exists');
return this._valueCache.get(key) !== null;
}
}

let addonSharedJSCache;
if (coreCacheAvail === true) {
log.debug('Caches from core are available, enable legacy cache methods to keep the old API.');
addonSharedJSCache = new JSCache(sharedCache, true);
} else {
log.debug('Caches from core are unavailable, using the addon-provided cache.');
addonSharedJSCache = new JSCache(addonSharedCache);
}

module.exports = {
get,
put,
remove,
exists
/** @deprecated */
get: (key, defaultSupplier) => { return addonSharedJSCache.get(key, defaultSupplier); },
/** @deprecated */
put: (key, value) => { return addonSharedJSCache.put(key, value); },
/** @deprecated */
remove: (key) => { return addonSharedJSCache.remove(key); },
/** @deprecated */
exists: (key) => { return addonSharedJSCache.exists(key); },
/**
* Shared cache that is shared across all rules and scripts, it can therefore be accessed from any automation language.
* The access to every key is tracked and the key is removed when all scripts that ever accessed that key are unloaded.
* If the key that has been auto-removed stored a timer, that timer is cancelled.
*
* @memberof cache
* @type JSCache
*/
shared: (coreCacheAvail === true) ? new JSCache(sharedCache) : undefined,
/**
* Private cache for each script.
* The private cache can only be accessed by the same script and is cleared when the script is unloaded.
* You can use it to e.g. store timers or counters between subsequent runs of that script.
* When the script is unloaded and the cache is cleared, all timers in the cache are cancelled.
*
* @memberof cache
* @type JSCache
*/
private: (coreCacheAvail === true) ? new JSCache(privateCache) : undefined,
JSCache
};
Loading

0 comments on commit fd61b53

Please sign in to comment.