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