committed by
GitHub
56 changed files with 1454 additions and 295 deletions
@ -0,0 +1,37 @@ |
|||
/** |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.entitiy.cf; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.id.CalculatedFieldId; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
|
|||
@Data |
|||
public class CalculatedFieldCtx { |
|||
|
|||
private CalculatedFieldId calculatedFieldId; |
|||
private EntityId entityId; |
|||
private CalculatedFieldState state; |
|||
|
|||
public CalculatedFieldCtx() { |
|||
} |
|||
|
|||
public CalculatedFieldCtx(CalculatedFieldId calculatedFieldId, EntityId entityId, CalculatedFieldState state) { |
|||
this.calculatedFieldId = calculatedFieldId; |
|||
this.entityId = entityId; |
|||
this.state = state; |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
/** |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.entitiy.cf; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnore; |
|||
import com.fasterxml.jackson.annotation.JsonSubTypes; |
|||
import com.fasterxml.jackson.annotation.JsonTypeInfo; |
|||
import org.thingsboard.server.common.data.cf.CalculatedFieldConfiguration; |
|||
import org.thingsboard.server.common.data.cf.CalculatedFieldType; |
|||
|
|||
import java.util.Map; |
|||
|
|||
@JsonTypeInfo( |
|||
use = JsonTypeInfo.Id.NAME, |
|||
include = JsonTypeInfo.As.PROPERTY, |
|||
property = "type" |
|||
) |
|||
@JsonSubTypes({ |
|||
@JsonSubTypes.Type(value = SimpleCalculatedFieldState.class, name = "SIMPLE") |
|||
}) |
|||
public interface CalculatedFieldState { |
|||
|
|||
@JsonIgnore |
|||
CalculatedFieldType getType(); |
|||
|
|||
void performCalculation(Map<String, String> argumentValues, CalculatedFieldConfiguration calculatedFieldConfiguration, boolean initialCalculation); |
|||
|
|||
} |
|||
@ -0,0 +1,95 @@ |
|||
/** |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.entitiy.cf; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.rocksdb.RocksDB; |
|||
import org.rocksdb.RocksDBException; |
|||
import org.rocksdb.RocksIterator; |
|||
import org.rocksdb.WriteBatch; |
|||
import org.rocksdb.WriteOptions; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.utils.RocksDBConfig; |
|||
|
|||
import java.nio.charset.StandardCharsets; |
|||
import java.util.HashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
|
|||
@Service |
|||
@Slf4j |
|||
public class RocksDBService { |
|||
|
|||
private final RocksDB db; |
|||
private final WriteOptions writeOptions; |
|||
|
|||
public RocksDBService(RocksDBConfig config) throws RocksDBException { |
|||
this.db = config.getDb(); |
|||
this.writeOptions = new WriteOptions().setSync(true); |
|||
} |
|||
|
|||
public void put(String key, String value) { |
|||
try { |
|||
db.put(writeOptions, key.getBytes(StandardCharsets.UTF_8), value.getBytes(StandardCharsets.UTF_8)); |
|||
} catch (RocksDBException e) { |
|||
log.error("Failed to store data to RocksDB", e); |
|||
} |
|||
} |
|||
|
|||
public void delete(String key) { |
|||
try { |
|||
db.delete(writeOptions, key.getBytes(StandardCharsets.UTF_8)); |
|||
} catch (RocksDBException e) { |
|||
log.error("Failed to delete data from RocksDB", e); |
|||
} |
|||
} |
|||
|
|||
public void deleteAll(List<String> keys) { |
|||
try (WriteBatch batch = new WriteBatch()) { |
|||
for (String key : keys) { |
|||
batch.delete(key.getBytes(StandardCharsets.UTF_8)); |
|||
} |
|||
db.write(writeOptions, batch); |
|||
} catch (RocksDBException e) { |
|||
log.error("Failed to delete data from RocksDB", e); |
|||
} |
|||
} |
|||
|
|||
public String get(String key) { |
|||
try { |
|||
byte[] value = db.get(key.getBytes(StandardCharsets.UTF_8)); |
|||
return value != null ? new String(value, StandardCharsets.UTF_8) : null; |
|||
} catch (RocksDBException e) { |
|||
log.error("Failed to retrieve data from RocksDB", e); |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
public Map<String, String> getAll() { |
|||
Map<String, String> map = new HashMap<>(); |
|||
try (RocksIterator iterator = db.newIterator()) { |
|||
for (iterator.seekToFirst(); iterator.isValid(); iterator.next()) { |
|||
String key = new String(iterator.key(), StandardCharsets.UTF_8); |
|||
String value = new String(iterator.value(), StandardCharsets.UTF_8); |
|||
map.put(key, value); |
|||
} |
|||
} catch (Exception e) { |
|||
log.error("Failed to retrieve data from RocksDB", e); |
|||
} |
|||
return map; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
/** |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.entitiy.cf; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.cf.CalculatedFieldConfiguration; |
|||
import org.thingsboard.server.common.data.cf.CalculatedFieldType; |
|||
|
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
|
|||
@Data |
|||
public class SimpleCalculatedFieldState implements CalculatedFieldState { |
|||
|
|||
// TODO: use value object(TsKv) instead of string
|
|||
Map<String, String> arguments = new HashMap<>(); |
|||
String result; |
|||
|
|||
@Override |
|||
public CalculatedFieldType getType() { |
|||
return CalculatedFieldType.SIMPLE; |
|||
} |
|||
|
|||
@Override |
|||
public void performCalculation(Map<String, String> argumentValues, CalculatedFieldConfiguration calculatedFieldConfiguration, boolean initialCalculation) { |
|||
if (initialCalculation) { |
|||
// todo: perform initial calculation
|
|||
this.arguments = argumentValues; |
|||
} else { |
|||
// todo: perform calculation based on previous data
|
|||
this.arguments.putAll(argumentValues); |
|||
} |
|||
this.result = "result"; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
/** |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.utils; |
|||
|
|||
import jakarta.annotation.PreDestroy; |
|||
import org.rocksdb.Options; |
|||
import org.rocksdb.RocksDB; |
|||
import org.rocksdb.RocksDBException; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
@Component |
|||
public class RocksDBConfig { |
|||
|
|||
@Value("${rocksdb.db_path:${java.io.tmpdir}/rocksdb}") |
|||
private String dbPath; |
|||
private RocksDB db; |
|||
|
|||
static { |
|||
RocksDB.loadLibrary(); |
|||
} |
|||
|
|||
public RocksDB getDb() throws RocksDBException { |
|||
if (db == null) { |
|||
Options options = new Options().setCreateIfMissing(true); |
|||
db = RocksDB.open(options, dbPath); |
|||
} |
|||
return db; |
|||
} |
|||
|
|||
@PreDestroy |
|||
public void close() { |
|||
if (db != null) { |
|||
db.close(); |
|||
db = null; |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,161 @@ |
|||
/** |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.common.data.cf; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnore; |
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import com.fasterxml.jackson.databind.ObjectMapper; |
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.EntityIdFactory; |
|||
|
|||
import java.util.HashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.Objects; |
|||
import java.util.UUID; |
|||
import java.util.stream.Collectors; |
|||
|
|||
@Data |
|||
public abstract class BaseCalculatedFieldConfiguration implements CalculatedFieldConfiguration { |
|||
|
|||
@JsonIgnore |
|||
private final ObjectMapper mapper = new ObjectMapper(); |
|||
|
|||
protected Map<String, Argument> arguments; |
|||
protected Output output; |
|||
|
|||
public BaseCalculatedFieldConfiguration() { |
|||
} |
|||
|
|||
public BaseCalculatedFieldConfiguration(JsonNode config, EntityType entityType, UUID entityId) { |
|||
BaseCalculatedFieldConfiguration calculatedFieldConfig = toCalculatedFieldConfig(config, entityType, entityId); |
|||
this.arguments = calculatedFieldConfig.getArguments(); |
|||
this.output = calculatedFieldConfig.getOutput(); |
|||
} |
|||
|
|||
@Override |
|||
public List<EntityId> getReferencedEntities() { |
|||
return arguments.values().stream() |
|||
.map(Argument::getEntityId) |
|||
.filter(Objects::nonNull) |
|||
.collect(Collectors.toList()); |
|||
} |
|||
|
|||
@Override |
|||
public CalculatedFieldLinkConfiguration getReferencedEntityConfig(EntityId entityId) { |
|||
CalculatedFieldLinkConfiguration linkConfiguration = new CalculatedFieldLinkConfiguration(); |
|||
arguments.values().stream() |
|||
.filter(argument -> argument.getEntityId().equals(entityId)) |
|||
.forEach(argument -> { |
|||
switch (argument.getType()) { |
|||
case "ATTRIBUTES": |
|||
linkConfiguration.getAttributes().add(argument.getKey()); |
|||
break; |
|||
case "TIME_SERIES": |
|||
linkConfiguration.getTimeSeries().add(argument.getKey()); |
|||
break; |
|||
} |
|||
}); |
|||
|
|||
return linkConfiguration; |
|||
} |
|||
|
|||
@Override |
|||
public JsonNode calculatedFieldConfigToJson(EntityType entityType, UUID entityId) { |
|||
ObjectNode configNode = mapper.createObjectNode(); |
|||
|
|||
ObjectNode argumentsNode = configNode.putObject("arguments"); |
|||
arguments.forEach((key, argument) -> { |
|||
ObjectNode argumentNode = argumentsNode.putObject(key); |
|||
EntityId referencedEntityId = argument.getEntityId(); |
|||
if (referencedEntityId != null) { |
|||
argumentNode.put("entityType", referencedEntityId.getEntityType().name()); |
|||
argumentNode.put("entityId", referencedEntityId.getId().toString()); |
|||
} else { |
|||
argumentNode.put("entityType", entityType.name()); |
|||
argumentNode.put("entityId", entityId.toString()); |
|||
} |
|||
argumentNode.put("key", argument.getKey()); |
|||
argumentNode.put("type", argument.getType()); |
|||
argumentNode.put("defaultValue", argument.getDefaultValue()); |
|||
}); |
|||
|
|||
if (output != null) { |
|||
ObjectNode outputNode = configNode.putObject("output"); |
|||
outputNode.put("type", output.getType()); |
|||
outputNode.put("expression", output.getExpression()); |
|||
} |
|||
|
|||
return configNode; |
|||
} |
|||
|
|||
@Data |
|||
public static class Argument { |
|||
private EntityId entityId; |
|||
private String key; |
|||
private String type; |
|||
private String defaultValue; |
|||
} |
|||
|
|||
@Data |
|||
public static class Output { |
|||
private String name; |
|||
private String type; |
|||
private String expression; |
|||
} |
|||
|
|||
private BaseCalculatedFieldConfiguration toCalculatedFieldConfig(JsonNode config, EntityType entityType, UUID entityId) { |
|||
if (config == null || !config.isObject()) { |
|||
return null; |
|||
} |
|||
|
|||
Map<String, Argument> arguments = new HashMap<>(); |
|||
JsonNode argumentsNode = config.get("arguments"); |
|||
if (argumentsNode != null && argumentsNode.isObject()) { |
|||
argumentsNode.fields().forEachRemaining(entry -> { |
|||
String key = entry.getKey(); |
|||
JsonNode argumentNode = entry.getValue(); |
|||
Argument argument = new Argument(); |
|||
if (argumentNode.hasNonNull("entityType") && argumentNode.hasNonNull("entityId")) { |
|||
String referencedEntityType = argumentNode.get("entityType").asText(); |
|||
UUID referencedEntityId = UUID.fromString(argumentNode.get("entityId").asText()); |
|||
argument.setEntityId(EntityIdFactory.getByTypeAndUuid(referencedEntityType, referencedEntityId)); |
|||
} else { |
|||
argument.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId)); |
|||
} |
|||
argument.setKey(argumentNode.get("key").asText()); |
|||
argument.setType(argumentNode.get("type").asText()); |
|||
argument.setDefaultValue(argumentNode.get("defaultValue").asText()); |
|||
arguments.put(key, argument); |
|||
}); |
|||
} |
|||
this.setArguments(arguments); |
|||
|
|||
JsonNode outputNode = config.get("output"); |
|||
if (outputNode != null) { |
|||
Output output = new Output(); |
|||
output.setType(outputNode.get("type").asText()); |
|||
output.setExpression(outputNode.get("expression").asText()); |
|||
this.setOutput(output); |
|||
} |
|||
|
|||
return this; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
/** |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.common.data.cf; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnore; |
|||
import com.fasterxml.jackson.annotation.JsonSubTypes; |
|||
import com.fasterxml.jackson.annotation.JsonTypeInfo; |
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
|
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.UUID; |
|||
|
|||
@JsonTypeInfo( |
|||
use = JsonTypeInfo.Id.NAME, |
|||
include = JsonTypeInfo.As.PROPERTY, |
|||
property = "type" |
|||
) |
|||
@JsonSubTypes({ |
|||
@JsonSubTypes.Type(value = SimpleCalculatedFieldConfiguration.class, name = "SIMPLE") |
|||
}) |
|||
public interface CalculatedFieldConfiguration { |
|||
|
|||
@JsonIgnore |
|||
CalculatedFieldType getType(); |
|||
|
|||
Map<String, BaseCalculatedFieldConfiguration.Argument> getArguments(); |
|||
|
|||
BaseCalculatedFieldConfiguration.Output getOutput(); |
|||
|
|||
@JsonIgnore |
|||
List<EntityId> getReferencedEntities(); |
|||
|
|||
@JsonIgnore |
|||
CalculatedFieldLinkConfiguration getReferencedEntityConfig(EntityId entityId); |
|||
|
|||
@JsonIgnore |
|||
JsonNode calculatedFieldConfigToJson(EntityType entityType, UUID entityId); |
|||
|
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
/** |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.common.data.cf; |
|||
|
|||
public enum CalculatedFieldType { |
|||
|
|||
SIMPLE, SCRIPT |
|||
|
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
/** |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.common.data.cf; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
@Data |
|||
public class SimpleCalculatedFieldConfiguration extends BaseCalculatedFieldConfiguration implements CalculatedFieldConfiguration { |
|||
|
|||
public SimpleCalculatedFieldConfiguration() { |
|||
super(); |
|||
} |
|||
|
|||
public SimpleCalculatedFieldConfiguration(JsonNode config, EntityType entityType, UUID entityId) { |
|||
super(config, entityType, entityId); |
|||
} |
|||
|
|||
@Override |
|||
public CalculatedFieldType getType() { |
|||
return CalculatedFieldType.SIMPLE; |
|||
} |
|||
} |
|||
@ -1,117 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.cf; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.cf.CalculatedFieldConfig; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.EntityIdFactory; |
|||
|
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
import java.util.UUID; |
|||
|
|||
public class CalculatedFieldConfigUtil { |
|||
|
|||
public static CalculatedFieldConfig toCalculatedFieldConfig(JsonNode config, EntityType entityType, UUID entityId) { |
|||
if (config == null) { |
|||
return null; |
|||
} |
|||
try { |
|||
CalculatedFieldConfig calculatedFieldConfig = new CalculatedFieldConfig(); |
|||
Map<String, CalculatedFieldConfig.Argument> arguments = new HashMap<>(); |
|||
|
|||
JsonNode argumentsNode = config.get("arguments"); |
|||
if (argumentsNode != null && argumentsNode.isObject()) { |
|||
argumentsNode.fields().forEachRemaining(entry -> { |
|||
String key = entry.getKey(); |
|||
JsonNode argumentNode = entry.getValue(); |
|||
|
|||
CalculatedFieldConfig.Argument argument = new CalculatedFieldConfig.Argument(); |
|||
if (argumentNode.has("entityType") && argumentNode.has("entityId")) { |
|||
String referencedEntityType = argumentNode.get("entityType").asText(); |
|||
UUID referencedEntityId = UUID.fromString(argumentNode.get("entityId").asText()); |
|||
argument.setEntityId(EntityIdFactory.getByTypeAndUuid(referencedEntityType, referencedEntityId)); |
|||
} else { |
|||
argument.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId)); |
|||
} |
|||
argument.setKey(argumentNode.get("key").asText()); |
|||
argument.setType(argumentNode.get("type").asText()); |
|||
|
|||
if (argumentNode.has("defaultValue")) { |
|||
argument.setDefaultValue(argumentNode.get("defaultValue").asInt()); |
|||
} |
|||
|
|||
arguments.put(key, argument); |
|||
}); |
|||
} |
|||
calculatedFieldConfig.setArguments(arguments); |
|||
|
|||
JsonNode outputNode = config.get("output"); |
|||
if (outputNode != null) { |
|||
CalculatedFieldConfig.Output output = new CalculatedFieldConfig.Output(); |
|||
output.setType(outputNode.get("type").asText()); |
|||
output.setExpression(outputNode.get("expression").asText()); |
|||
calculatedFieldConfig.setOutput(output); |
|||
} |
|||
|
|||
return calculatedFieldConfig; |
|||
|
|||
} catch (Exception e) { |
|||
throw new IllegalArgumentException("Failed to convert JsonNode to CalculatedFieldConfig", e); |
|||
} |
|||
} |
|||
|
|||
public static JsonNode calculatedFieldConfigToJson(CalculatedFieldConfig calculatedFieldConfig, EntityType entityType, UUID entityId) { |
|||
if (calculatedFieldConfig == null) { |
|||
return null; |
|||
} |
|||
try { |
|||
ObjectNode configNode = JacksonUtil.newObjectNode(); |
|||
|
|||
ObjectNode argumentsNode = configNode.putObject("arguments"); |
|||
calculatedFieldConfig.getArguments().forEach((key, argument) -> { |
|||
ObjectNode argumentNode = argumentsNode.putObject(key); |
|||
EntityId referencedEntityId = argument.getEntityId(); |
|||
if (referencedEntityId != null) { |
|||
argumentNode.put("entityType", referencedEntityId.getEntityType().name()); |
|||
argumentNode.put("entityId", referencedEntityId.getId().toString()); |
|||
} else { |
|||
argumentNode.put("entityType", entityType.name()); |
|||
argumentNode.put("entityId", entityId.toString()); |
|||
} |
|||
argumentNode.put("key", argument.getKey()); |
|||
argumentNode.put("type", argument.getType()); |
|||
argumentNode.put("defaultValue", argument.getDefaultValue()); |
|||
}); |
|||
|
|||
if (calculatedFieldConfig.getOutput() != null) { |
|||
ObjectNode outputNode = configNode.putObject("output"); |
|||
outputNode.put("type", calculatedFieldConfig.getOutput().getType()); |
|||
outputNode.put("expression", calculatedFieldConfig.getOutput().getExpression()); |
|||
} |
|||
|
|||
return configNode; |
|||
|
|||
} catch (Exception e) { |
|||
throw new IllegalArgumentException("Failed to convert CalculatedFieldConfig to JsonNode", e); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,146 @@ |
|||
/** |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.sql.cf; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.data.domain.Pageable; |
|||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; |
|||
import org.springframework.stereotype.Repository; |
|||
import org.springframework.transaction.support.TransactionTemplate; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.cf.CalculatedField; |
|||
import org.thingsboard.server.common.data.cf.CalculatedFieldConfiguration; |
|||
import org.thingsboard.server.common.data.cf.CalculatedFieldLink; |
|||
import org.thingsboard.server.common.data.cf.CalculatedFieldLinkConfiguration; |
|||
import org.thingsboard.server.common.data.cf.CalculatedFieldType; |
|||
import org.thingsboard.server.common.data.cf.SimpleCalculatedFieldConfiguration; |
|||
import org.thingsboard.server.common.data.id.CalculatedFieldId; |
|||
import org.thingsboard.server.common.data.id.CalculatedFieldLinkId; |
|||
import org.thingsboard.server.common.data.id.EntityIdFactory; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
|
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.UUID; |
|||
import java.util.stream.Collectors; |
|||
|
|||
@RequiredArgsConstructor |
|||
@Repository |
|||
@Slf4j |
|||
public class DefaultNativeCalculatedFieldRepository implements NativeCalculatedFieldRepository { |
|||
|
|||
private final String CF_COUNT_QUERY = "SELECT count(id) FROM calculated_field;"; |
|||
private final String CF_QUERY = "SELECT * FROM calculated_field ORDER BY created_time ASC LIMIT %s OFFSET %s"; |
|||
|
|||
private final String CFL_COUNT_QUERY = "SELECT count(id) FROM calculated_field_link;"; |
|||
private final String CFL_QUERY = "SELECT * FROM calculated_field_link ORDER BY created_time ASC LIMIT %s OFFSET %s"; |
|||
|
|||
private final NamedParameterJdbcTemplate jdbcTemplate; |
|||
private final TransactionTemplate transactionTemplate; |
|||
|
|||
@Override |
|||
public PageData<CalculatedField> findCalculatedFields(Pageable pageable) { |
|||
return transactionTemplate.execute(status -> { |
|||
long startTs = System.currentTimeMillis(); |
|||
int totalElements = jdbcTemplate.queryForObject(CF_COUNT_QUERY, Collections.emptyMap(), Integer.class); |
|||
log.debug("Count query took {} ms", System.currentTimeMillis() - startTs); |
|||
startTs = System.currentTimeMillis(); |
|||
List<Map<String, Object>> rows = jdbcTemplate.queryForList(String.format(CF_QUERY, pageable.getPageSize(), pageable.getOffset()), Collections.emptyMap()); |
|||
log.debug("Main query took {} ms", System.currentTimeMillis() - startTs); |
|||
int totalPages = pageable.getPageSize() > 0 ? (int) Math.ceil((float) totalElements / pageable.getPageSize()) : 1; |
|||
boolean hasNext = pageable.getPageSize() > 0 && totalElements > pageable.getOffset() + rows.size(); |
|||
var data = rows.stream().map(row -> { |
|||
|
|||
UUID id = (UUID) row.get("id"); |
|||
long createdTime = (long) row.get("created_time"); |
|||
UUID tenantId = (UUID) row.get("tenant_id"); |
|||
EntityType entityType = EntityType.valueOf((String) row.get("entity_type")); |
|||
UUID entityId = (UUID) row.get("entity_id"); |
|||
CalculatedFieldType type = CalculatedFieldType.valueOf((String) row.get("type")); |
|||
String name = (String) row.get("name"); |
|||
int configurationVersion = (int) row.get("configuration_version"); |
|||
JsonNode configuration = JacksonUtil.toJsonNode((String) row.get("configuration")); |
|||
long version = (long) row.get("version"); |
|||
Object externalIdObj = row.get("external_id"); |
|||
|
|||
CalculatedField calculatedField = new CalculatedField(); |
|||
calculatedField.setId(new CalculatedFieldId(id)); |
|||
calculatedField.setCreatedTime(createdTime); |
|||
calculatedField.setTenantId(new TenantId(tenantId)); |
|||
calculatedField.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId)); |
|||
calculatedField.setType(type); |
|||
calculatedField.setName(name); |
|||
calculatedField.setConfigurationVersion(configurationVersion); |
|||
calculatedField.setConfiguration(readCalculatedFieldConfiguration(type, configuration, entityType, entityId)); |
|||
calculatedField.setVersion(version); |
|||
calculatedField.setExternalId(externalIdObj != null ? new CalculatedFieldId(UUID.fromString((String) externalIdObj)) : null); |
|||
|
|||
return calculatedField; |
|||
}).collect(Collectors.toList()); |
|||
return new PageData<>(data, totalPages, totalElements, hasNext); |
|||
}); |
|||
} |
|||
|
|||
@Override |
|||
public PageData<CalculatedFieldLink> findCalculatedFieldLinks(Pageable pageable) { |
|||
return transactionTemplate.execute(status -> { |
|||
long startTs = System.currentTimeMillis(); |
|||
int totalElements = jdbcTemplate.queryForObject(CFL_COUNT_QUERY, Collections.emptyMap(), Integer.class); |
|||
log.debug("Count query took {} ms", System.currentTimeMillis() - startTs); |
|||
startTs = System.currentTimeMillis(); |
|||
List<Map<String, Object>> rows = jdbcTemplate.queryForList(String.format(CFL_QUERY, pageable.getPageSize(), pageable.getOffset()), Collections.emptyMap()); |
|||
log.debug("Main query took {} ms", System.currentTimeMillis() - startTs); |
|||
int totalPages = pageable.getPageSize() > 0 ? (int) Math.ceil((float) totalElements / pageable.getPageSize()) : 1; |
|||
boolean hasNext = pageable.getPageSize() > 0 && totalElements > pageable.getOffset() + rows.size(); |
|||
var data = rows.stream().map(row -> { |
|||
|
|||
UUID id = (UUID) row.get("id"); |
|||
long createdTime = (long) row.get("created_time"); |
|||
UUID tenantId = (UUID) row.get("tenant_id"); |
|||
EntityType entityType = EntityType.valueOf((String) row.get("entity_type")); |
|||
UUID entityId = (UUID) row.get("entity_id"); |
|||
UUID calculatedFieldId = (UUID) row.get("calculated_field_id"); |
|||
JsonNode configuration = JacksonUtil.toJsonNode((String) row.get("configuration")); |
|||
|
|||
CalculatedFieldLink calculatedFieldLink = new CalculatedFieldLink(); |
|||
calculatedFieldLink.setId(new CalculatedFieldLinkId(id)); |
|||
calculatedFieldLink.setCreatedTime(createdTime); |
|||
calculatedFieldLink.setTenantId(new TenantId(tenantId)); |
|||
calculatedFieldLink.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId)); |
|||
calculatedFieldLink.setCalculatedFieldId(new CalculatedFieldId(calculatedFieldId)); |
|||
calculatedFieldLink.setConfiguration(JacksonUtil.treeToValue(configuration, CalculatedFieldLinkConfiguration.class)); |
|||
|
|||
return calculatedFieldLink; |
|||
}).collect(Collectors.toList()); |
|||
return new PageData<>(data, totalPages, totalElements, hasNext); |
|||
}); |
|||
} |
|||
|
|||
private CalculatedFieldConfiguration readCalculatedFieldConfiguration(CalculatedFieldType type, JsonNode config, EntityType entityType, UUID entityId) { |
|||
switch (type) { |
|||
case SIMPLE: |
|||
return new SimpleCalculatedFieldConfiguration(config, entityType, entityId); |
|||
default: |
|||
throw new IllegalArgumentException("Unsupported calculated field type: " + type + "!"); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
/** |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.sql.cf; |
|||
|
|||
import org.springframework.data.domain.Pageable; |
|||
import org.thingsboard.server.common.data.cf.CalculatedField; |
|||
import org.thingsboard.server.common.data.cf.CalculatedFieldLink; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
|
|||
public interface NativeCalculatedFieldRepository { |
|||
|
|||
PageData<CalculatedField> findCalculatedFields(Pageable pageable); |
|||
|
|||
PageData<CalculatedFieldLink> findCalculatedFieldLinks(Pageable pageable); |
|||
|
|||
} |
|||
Loading…
Reference in new issue