Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[cache] Reimplementation to use the new core caches #191

Merged
merged 10 commits into from
Dec 11, 2022
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 @@ -686,34 +686,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
167 changes: 104 additions & 63 deletions cache.js
Original file line number Diff line number Diff line change
@@ -1,77 +1,118 @@
/**
* 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) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any way we can get the ruleUID or filename to log out as part of this warning? At first some users may get hundreds or these warnings and knowing which rules they are coming from can help in prioritization in dealing with them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By using console.warn instead of log.warn we are logging to the logger name of the script/rule, so for file-based script to org.openhab.automation.script.file.filename.js and for UI to org.openhab.automation.script.ui.ruleUid.

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); },
/**
* 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.
*
* @memberof cache
* @type JSCache
*/
shared: (coreCacheAvail === true) ? new JSCache(sharedCache) : undefined,
/**
* 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.
*
* @memberof cache
* @type JSCache
*/
private: (coreCacheAvail === true) ? new JSCache(privateCache) : undefined,
JSCache
};
Loading