Skip to content

Commit

Permalink
[items] ItemPersistence: Convert state to primitive type in persist
Browse files Browse the repository at this point in the history
… method (#339)

This fixes an issue, where the asynchronous way persistence services
store the passed in state causes a IllegalStateException by GraalJS,
because multithreaded access to the script's context is not allowed.

See
openhab/openhab-core#4268 (comment).

---------

Signed-off-by: Florian Hotze <[email protected]>
  • Loading branch information
florian-h05 authored Jun 7, 2024
1 parent d81a61f commit 89692b1
Show file tree
Hide file tree
Showing 5 changed files with 33 additions and 18 deletions.
14 changes: 8 additions & 6 deletions src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,19 @@ function _getItemName (itemOrName) {
}

/**
* Helper function to convert JS types to a
* {@link https://www.openhab.org/javadoc/latest/org/openhab/core/types/type org.openhab.core.types.Type} or a valid string representation of a type.
* Helper function to convert a JS type to a primitive type accepted by openHAB Core, which often is a string representation of the type.
*
* Number and string are passed through.
* Other objects should implement <code>toOpenHabString</code> (prioritized) or <code>toString</code> to return an openHAB compatible representation.
* Converting any complex type to a primitive type is required to avoid multi-threading issues (as GraalJS does not allow multithreaded access to a script's context),
* e.g. when passing objects to persistence, which then persists asynchronously.
*
* Number and string primitives are passed through.
* Objects should implement <code>toOpenHabString</code> (prioritized) or <code>toString</code> to return an openHAB Core compatible string representation.
*
* @private
* @param {*} value
* @returns {*}
*/
function _toOpenhabType (value) {
function _toOpenhabPrimitiveType (value) {
if (value === null) return 'NULL';
if (value === undefined) return 'UNDEF';
if (typeof value === 'number' || typeof value === 'string') {
Expand Down Expand Up @@ -107,7 +109,7 @@ function _isDuration (o) {

module.exports = {
_getItemName,
_toOpenhabType,
_toOpenhabPrimitiveType,
_isItem,
_isQuantity,
_isZonedDateTime,
Expand Down
19 changes: 16 additions & 3 deletions src/items/item-persistence.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const utils = require('../utils');
* @private
*/
const { getQuantity, QuantityError } = require('../quantity');
const { _toOpenhabPrimitiveType } = require('../helpers');
const PersistenceExtensions = Java.type('org.openhab.core.persistence.extensions.PersistenceExtensions');

/**
Expand Down Expand Up @@ -135,7 +136,7 @@ class ItemPersistence {
}

/**
* Persists the state of a given Item.
* Persists a state of a given Item.
*
* There are four ways to use this method:
* ```js
Expand All @@ -158,11 +159,23 @@ class ItemPersistence {
* ```
*
* @param {(time.ZonedDateTime | Date)} [timestamp] the date for the item state to be stored
* @param {string} [state] the state to be stored
* @param {string|number|time.ZonedDateTime|Quantity|HostState} [state] the state to be stored
* @param {string} [serviceId] optional persistence service ID, if omitted, the default persistence service will be used
*/
persist (timestamp, state, serviceId) {
PersistenceExtensions.persist(this.rawItem, ...arguments);
switch (arguments.length) {
// persist a given state at a given timestamp
case 2:
PersistenceExtensions.persist(this.rawItem, timestamp, _toOpenhabPrimitiveType(state));
break;
case 3:
PersistenceExtensions.persist(this.rawItem, timestamp, _toOpenhabPrimitiveType(state), serviceId);
break;
// persist the current state or a TimeSeries
default:
PersistenceExtensions.persist(this.rawItem, ...arguments);
break;
}
}

// TODO: Add persist for TimeSeries
Expand Down
8 changes: 4 additions & 4 deletions src/items/items.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
const osgi = require('../osgi');
const utils = require('../utils');
const log = require('../log')('items');
const { _toOpenhabType } = require('../helpers');
const { _toOpenhabPrimitiveType } = require('../helpers');
const { getQuantity, QuantityError } = require('../quantity');

const { UnDefType, OnOffType, events, itemRegistry } = require('@runtime');
Expand Down Expand Up @@ -234,7 +234,7 @@ class Item {
* @see postUpdate
*/
sendCommand (value) {
events.sendCommand(this.rawItem, _toOpenhabType(value));
events.sendCommand(this.rawItem, _toOpenhabPrimitiveType(value));
}

/**
Expand All @@ -245,7 +245,7 @@ class Item {
* @see sendCommand
*/
sendCommandIfDifferent (value) {
value = _toOpenhabType(value);
value = _toOpenhabPrimitiveType(value);
if (value.toString() !== this.state) {
this.sendCommand(value);
return true;
Expand Down Expand Up @@ -309,7 +309,7 @@ class Item {
* @see sendCommand
*/
postUpdate (value) {
events.postUpdate(this.rawItem, _toOpenhabType(value));
events.postUpdate(this.rawItem, _toOpenhabPrimitiveType(value));
}

/**
Expand Down
8 changes: 4 additions & 4 deletions types/items/item-persistence.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ declare class ItemPersistence {
constructor(rawItem: any);
rawItem: any;
/**
* Persists the state of a given Item.
* Persists a state of a given Item.
*
* There are four ways to use this method:
* ```js
Expand All @@ -38,10 +38,10 @@ declare class ItemPersistence {
* ```
*
* @param {(time.ZonedDateTime | Date)} [timestamp] the date for the item state to be stored
* @param {string} [state] the state to be stored
* @param {string|number|time.ZonedDateTime|Quantity|HostState} [state] the state to be stored
* @param {string} [serviceId] optional persistence service ID, if omitted, the default persistence service will be used
*/
persist(timestamp?: (time.ZonedDateTime | Date), state?: string, serviceId?: string, ...args: any[]): void;
persist(timestamp?: (time.ZonedDateTime | Date), state?: string | number | time.ZonedDateTime | Quantity | HostState, serviceId?: string, ...args: any[]): void;
/**
* Retrieves the persisted state for a given Item at a certain point in time.
*
Expand Down Expand Up @@ -477,6 +477,7 @@ declare namespace time {
type ZonedDateTime = import('@js-joda/core').ZonedDateTime;
}
import time = require("../time");
type Quantity = import('../quantity').Quantity;
/**
* Class representing an instance of {@link https://www.openhab.org/javadoc/latest/org/openhab/core/persistence/historicitem org.openhab.core.persistence.HistoricItem}.
* Extends {@link items.PersistedState}.
Expand Down Expand Up @@ -520,5 +521,4 @@ declare class PersistedState {
get quantityState(): import("../quantity").Quantity;
#private;
}
type Quantity = import('../quantity').Quantity;
//# sourceMappingURL=item-persistence.d.ts.map
2 changes: 1 addition & 1 deletion types/items/item-persistence.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 89692b1

Please sign in to comment.