Merge pull request #164 from pnoker/chore/promote-develop-main-3

chore: promote develop → main (quality #160-163)
This commit is contained in:
Henry Zhang
2026-08-03 23:23:24 +08:00
committed by GitHub
55 changed files with 266 additions and 170 deletions
@@ -28,7 +28,7 @@ import io.github.pnoker.common.agentic.tools.TenantTool;
import io.github.pnoker.common.agentic.tools.UserTool;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.ToolCallAdvisor;
import org.springframework.ai.chat.client.advisor.ToolCallingAdvisor;
import org.springframework.ai.chat.client.advisor.api.Advisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.ChatMemoryRepository;
@@ -95,7 +95,7 @@ public class ChatClientConfig {
@Bean
public Advisor agenticToolCallAdvisor(ToolCallingManager toolCallingManager) {
return ToolCallAdvisor.builder()
return ToolCallingAdvisor.builder()
.toolCallingManager(toolCallingManager)
.advisorOrder(Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER + 100)
.build();
@@ -39,6 +39,7 @@ import io.github.pnoker.common.enums.EnableFlagEnum;
import lombok.extern.slf4j.Slf4j;
import static io.github.pnoker.common.utils.LogSanitizer.sanitize;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
import org.springframework.ai.anthropic.AnthropicChatModel;
import org.springframework.ai.anthropic.AnthropicChatOptions;
import org.springframework.ai.chat.client.ChatClient;
@@ -123,7 +124,7 @@ public class ChatClientFactory {
}
String fallback = StringUtils.trimToNull(fallbackModel);
if (StringUtils.isNotBlank(fallback)) {
if (StringUtils.isNotBlank(candidate) && !StringUtils.equals(candidate, fallback)) {
if (StringUtils.isNotBlank(candidate) && !Strings.CS.equals(candidate, fallback)) {
log.warn("Agentic requested model is not configured, falling back to Spring AI model, requestedModel={}, fallbackModel={}",
sanitize(candidate), sanitize(fallback));
}
@@ -199,7 +200,7 @@ public class ChatClientFactory {
if (Objects.nonNull(config)) {
return Boolean.TRUE.equals(config.getToolCall());
}
return StringUtils.isNotBlank(fallbackModel) && StringUtils.equals(model, fallbackModel)
return StringUtils.isNotBlank(fallbackModel) && Strings.CS.equals(model, fallbackModel)
&& properties.isFallbackToolCallingEnabled();
}
@@ -83,7 +83,7 @@ public class AgenticPromptBuilder {
if (!prepared.toolCallingEnabled()) {
return promptSpec;
}
return promptSpec.toolCallbacks(toolCallbackProvider).advisors(toolCallAdvisor);
return promptSpec.tools(toolCallbackProvider).advisors(toolCallAdvisor);
}
private ChatClient.ChatClientRequestSpec applyRequestOptions(ChatClient.ChatClientRequestSpec promptSpec,
@@ -53,7 +53,7 @@ public class AgenticRunTrace {
public void recordPendingEvent(AgenticRunEvent event) {
if (Objects.nonNull(event)) {
pendingEvents.offer(event);
pendingEvents.add(event);
}
}
@@ -70,7 +70,7 @@ public class AgenticRunTrace {
public void recordPendingVisualization(AgenticVisualizationSpec visualization) {
if (Objects.nonNull(visualization)) {
pendingVisualizations.offer(visualization);
pendingVisualizations.add(visualization);
}
}
@@ -107,7 +107,7 @@ public class AgenticToolContextUtil {
Object value = getContextValue(toolContext, AgenticConstant.ToolContextKey.VISUALIZATIONS);
if (value instanceof Queue<?>) {
Queue<AgenticVisualizationSpec> queue = (Queue<AgenticVisualizationSpec>) value;
visualizations.stream().filter(Objects::nonNull).forEach(queue::offer);
visualizations.stream().filter(Objects::nonNull).forEach(queue::add);
}
}
@@ -118,7 +118,7 @@ public class AgenticToolContextUtil {
}
Object value = getContextValue(toolContext, AgenticConstant.ToolContextKey.RUN_EVENTS);
if (value instanceof Queue<?>) {
((Queue<AgenticRunEvent>) value).offer(event);
((Queue<AgenticRunEvent>) value).add(event);
}
}
@@ -38,7 +38,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.ai.chat.client.advisor.ToolCallAdvisor;
import org.springframework.ai.chat.client.advisor.ToolCallingAdvisor;
import org.springframework.ai.chat.client.advisor.api.Advisor;
import org.springframework.ai.model.tool.ToolCallingManager;
import org.springframework.ai.tool.ToolCallback;
@@ -139,8 +139,8 @@ class ChatClientConfigTest {
void agenticToolCallAdvisorRunsAfterMemoryAdvisor() {
ChatClientConfig config = new ChatClientConfig();
assertThat(config.agenticToolCallAdvisor(ToolCallingManager.builder().build()))
.isInstanceOf(ToolCallAdvisor.class)
.extracting(ToolCallAdvisor.class::cast)
.isInstanceOf(ToolCallingAdvisor.class)
.extracting(ToolCallingAdvisor.class::cast)
.satisfies(advisor -> {
assertThat(advisor.getName()).isEqualTo("Tool Calling Advisor");
assertThat(advisor.getOrder()).isEqualTo(Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER + 100);
@@ -76,12 +76,12 @@ class AgenticPromptBuilderTest {
@Test
void buildAttachesToolCallbacksAndExplicitToolCallAdvisorWhenToolCallingIsEnabled() {
when(promptSpec.toolCallbacks(toolCallbackProvider)).thenReturn(promptSpec);
when(promptSpec.tools(toolCallbackProvider)).thenReturn(promptSpec);
when(promptSpec.advisors(toolCallAdvisor)).thenReturn(promptSpec);
promptBuilder.build(prepared(true));
verify(promptSpec).toolCallbacks(toolCallbackProvider);
verify(promptSpec).tools(toolCallbackProvider);
verify(promptSpec).advisors(toolCallAdvisor);
}
@@ -104,7 +104,7 @@ class AgenticPromptBuilderTest {
@Test
void buildAdvertisesPlatformToolsWhenToolCallingIsEnabled() {
when(promptSpec.toolCallbacks(toolCallbackProvider)).thenReturn(promptSpec);
when(promptSpec.tools(toolCallbackProvider)).thenReturn(promptSpec);
when(promptSpec.advisors(toolCallAdvisor)).thenReturn(promptSpec);
promptBuilder.build(prepared(true));
@@ -1394,6 +1394,9 @@ public class OAuthMcpRuntimeServiceImpl implements OAuthMcpRuntimeService {
}
private Map<String, Object> orderedMap(Object... values) {
if (values.length % 2 != 0) {
throw new IllegalArgumentException("orderedMap requires key-value pairs (even number of arguments)");
}
Map<String, Object> map = new LinkedHashMap<>();
for (int i = 0; i < values.length; i += 2) {
map.put(String.valueOf(values[i]), values[i + 1]);
@@ -164,7 +164,6 @@ public class OAuthController {
/**
* Exchange an authorization code (or supported grant) for access and refresh tokens.
*
* @param body form-encoded token request parameters (grant_type, code, redirect_uri, code_verifier, etc.)
* @param authorizationHeader optional HTTP Basic client credentials
* @return a 200 response carrying the token JSON; OAuth protocol errors map to the spec status
*/
@@ -191,7 +190,6 @@ public class OAuthController {
/**
* Revoke a previously issued access or refresh token.
*
* @param body form-encoded revocation parameters (token and token_type_hint)
* @param authorizationHeader optional HTTP Basic client credentials
* @return a 200 response confirming the revocation; OAuth protocol errors map to the spec status
*/
@@ -111,12 +111,12 @@ public class McpOpenApiAggregator {
if (!paths.isObject()) {
continue;
}
paths.fields().forEachRemaining(pathEntry -> {
paths.properties().forEach(pathEntry -> {
JsonNode pathItem = pathEntry.getValue();
if (!pathItem.isObject()) {
return;
}
pathItem.fields().forEachRemaining(opEntry -> {
pathItem.properties().forEach(opEntry -> {
String method = opEntry.getKey().toUpperCase();
JsonNode operation = opEntry.getValue();
if (!operation.isObject()) {
@@ -192,7 +192,7 @@ public class McpOpenApiAggregator {
JsonNode resolved = resolveRefs(bodySchema, root, 0);
JsonNode bodyProps = resolved.path("properties");
if (bodyProps.isObject()) {
bodyProps.fields().forEachRemaining(f -> properties.set(f.getKey(), f.getValue()));
bodyProps.properties().forEach(f -> properties.set(f.getKey(), f.getValue()));
resolved.path("required").forEach(required::add);
}
}
@@ -251,7 +251,7 @@ public class McpOpenApiAggregator {
return node;
}
ObjectNode copy = objectMapper.createObjectNode();
node.fields().forEachRemaining(e -> copy.set(e.getKey(), resolveRefs(e.getValue(), root, depth + 1)));
node.properties().forEach(e -> copy.set(e.getKey(), resolveRefs(e.getValue(), root, depth + 1)));
return copy;
}
if (node.isArray()) {
@@ -241,7 +241,8 @@ public class GroupServiceImpl implements GroupService {
}
}
Byte parentLevel = Objects.isNull(parent.getGroupLevel()) ? 0 : parent.getGroupLevel();
Byte groupLevel = parent.getGroupLevel();
byte parentLevel = groupLevel == null ? 0 : groupLevel;
entityBO.setGroupLevel((byte) (parentLevel + 1));
}
@@ -80,13 +80,11 @@ public class AlarmRuleTriggerServiceImpl implements AlarmRuleTriggerService {
return;
}
List<PointValueBO> valid = new ArrayList<>();
List<RuleFact> facts = new ArrayList<>();
for (PointValueBO pointValue : pointValues) {
if (Objects.isNull(pointValue) || !isValidId(pointValue.getTenantId()) || !isValidId(pointValue.getPointId())) {
continue;
}
valid.add(pointValue);
LocalDateTime ts = factTime(pointValue.getCreateTime());
windowSampleBuffer.append(
WindowSampleKey.of(pointValue.getTenantId(), AlarmTargetTypeEnum.POINT, pointValue.getPointId()),
@@ -20,6 +20,7 @@ package io.github.pnoker.common.data.biz.alarm;
import io.github.pnoker.common.constant.common.BaseConstant;
import io.github.pnoker.common.entity.ext.RuleExt;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
import java.math.BigDecimal;
import java.util.Objects;
@@ -149,9 +150,9 @@ public final class ConditionEvaluator {
String actual = Objects.toString(value, "");
String expected = Objects.toString(condition.getExpected(), "");
return switch (operator) {
case "==", "eq" -> StringUtils.equals(actual, expected);
case "!=", "ne" -> !StringUtils.equals(actual, expected);
case "contains" -> StringUtils.contains(actual, expected);
case "==", "eq" -> Strings.CS.equals(actual, expected);
case "!=", "ne" -> !Strings.CS.equals(actual, expected);
case "contains" -> Strings.CS.contains(actual, expected);
default -> false;
};
}
@@ -22,6 +22,7 @@ import io.github.pnoker.common.entity.ext.MessageExt;
import io.github.pnoker.common.enums.NotifyChannelTypeEnum;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
@@ -83,8 +84,8 @@ public class MessageRenderServiceImpl implements MessageRenderService {
if (Objects.isNull(template) || StringUtils.isBlank(template.getChannelType())) {
return false;
}
return StringUtils.equalsIgnoreCase(template.getChannelType(), channelTypeFlag.name())
|| StringUtils.equalsIgnoreCase(template.getChannelType(), channelTypeFlag.getCode());
return Strings.CI.equals(template.getChannelType(), channelTypeFlag.name())
|| Strings.CI.equals(template.getChannelType(), channelTypeFlag.getCode());
}
/**
@@ -24,6 +24,7 @@ import io.github.pnoker.common.data.entity.bo.RuleStateBO;
import io.github.pnoker.common.entity.ext.NotifyChannelBindExt;
import io.github.pnoker.common.entity.ext.NotifyExt;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
import org.springframework.stereotype.Service;
import java.time.DateTimeException;
@@ -68,7 +69,7 @@ public class NotifyPolicyEngineImpl implements NotifyPolicyEngine {
}
private boolean isRecovery(RuleMatch match) {
return StringUtils.equalsIgnoreCase(match.getMatchType(), AlarmConstant.MATCH_TYPE_RECOVERY);
return Strings.CI.equals(match.getMatchType(), AlarmConstant.MATCH_TYPE_RECOVERY);
}
/**
@@ -104,7 +105,7 @@ public class NotifyPolicyEngineImpl implements NotifyPolicyEngine {
return true;
}
for (String level : bindContent.getLevels()) {
if (StringUtils.equalsIgnoreCase(level, match.getSeverity())) {
if (Strings.CI.equals(level, match.getSeverity())) {
return true;
}
}
@@ -222,8 +223,8 @@ public class NotifyPolicyEngineImpl implements NotifyPolicyEngine {
return true;
}
for (String day : daysOfWeek) {
if (StringUtils.equalsIgnoreCase(day, current.name())
|| StringUtils.equalsIgnoreCase(day, current.getDisplayName(java.time.format.TextStyle.SHORT,
if (Strings.CI.equals(day, current.name())
|| Strings.CI.equals(day, current.getDisplayName(java.time.format.TextStyle.SHORT,
Locale.ENGLISH))) {
return true;
}
@@ -31,6 +31,7 @@ import io.github.pnoker.common.utils.JsonUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
import org.springframework.stereotype.Service;
import java.util.Map;
@@ -67,7 +68,7 @@ public class RuleAlarmPersistenceServiceImpl implements RuleAlarmPersistenceServ
fact.setAlarmId(firingAlarmId);
return;
}
if (!StringUtils.equalsIgnoreCase(AlarmConstant.MATCH_TYPE_FIRING, match.getMatchType())) {
if (!Strings.CI.equals(AlarmConstant.MATCH_TYPE_FIRING, match.getMatchType())) {
return;
}
@@ -47,6 +47,7 @@ import io.github.pnoker.common.enums.RuleStatusEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -257,7 +258,7 @@ public class RuleNotificationServiceImpl implements RuleNotificationService {
RuleFact fact = match.getFact();
String fingerprint = fingerprint(match, notify, variables);
RuleStateBO state = loadState(rule, fact, fingerprint);
boolean isRecovery = StringUtils.equalsIgnoreCase(match.getMatchType(), AlarmConstant.MATCH_TYPE_RECOVERY);
boolean isRecovery = Strings.CI.equals(match.getMatchType(), AlarmConstant.MATCH_TYPE_RECOVERY);
if (isRecovery && (Objects.isNull(state) || !RuleStatusEnum.FIRING.equals(state.getEntityStateFlag()))) {
log.debug("Skip recovery state transition because no FIRING fingerprint exists, ruleId={}, entityId={}",
rule.getId(), fact.getEntityId());
@@ -683,7 +683,7 @@ public class DashboardServiceImpl implements DashboardService {
CoverageGapVO.Item it = new CoverageGapVO.Item();
it.setPointId(r.getPointId());
it.setProfileId(r.getProfileId());
vo.getItems().add(it);
vo.addItem(it);
}
// missingPoints = actual count; items may be capped. Use a second
// query only if we hit the cap — otherwise items.size() is authoritative.
@@ -150,8 +150,7 @@ public class PointCommandServiceImpl implements PointCommandService, PointComman
}
checkDriverOnline(tenantId, driver.getId());
FacadePointBO point = pointFacade.getById(tenantId, entityBO.getPointId());
pointCommandValidator.validateWriteValue(entityBO.getValue(), Objects.nonNull(point) ? point.getPointExt() : null);
pointCommandValidator.validateWriteValue(entityBO.getValue());
String commandId = resolveCommandId(entityBO.getCommandId());
LocalDateTime nowLocal = LocalDateTime.now();
@@ -26,6 +26,7 @@ import lombok.ToString;
import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
@@ -56,6 +57,25 @@ public class CoverageGapVO implements Serializable {
@Schema(description = "capped list of offending point/profile ids")
private List<Item> items = new ArrayList<>();
/**
* Unmodifiable view of the offending items, so callers cannot mutate internal state
* through the getter. Use {@link #addItem(Item)} to append.
*
* @return unmodifiable view of the items list
*/
public List<Item> getItems() {
return Collections.unmodifiableList(items);
}
/**
* Append one offending item to the internal list.
*
* @param item item to append
*/
public void addItem(Item item) {
items.add(item);
}
@Getter
@Setter
@ToString
@@ -17,7 +17,6 @@
package io.github.pnoker.common.data.validator;
import io.github.pnoker.common.entity.ext.PointExt;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@@ -40,11 +39,10 @@ public class PointCommandValidator {
/**
* Validate a write value against the point's constraints.
*
* @param value raw value string to validate
* @param pointExt point extension JSON (may contain constraints for future use)
* @param value raw value string to validate
* @throws IllegalArgumentException if the value fails validation
*/
public void validateWriteValue(String value, PointExt pointExt) {
public void validateWriteValue(String value) {
if (value == null || value.isBlank()) {
throw new IllegalArgumentException("Write value must not be blank");
}
@@ -49,11 +49,6 @@ public class DataInitRunner implements ApplicationRunner {
private final ScheduleForDataService scheduleForDataService;
/**
* Constructor for DataInitRunner
*
* @param scheduleForDataService Service for handling data scheduling operations
*/
/**
* Executes the data initialization process when the application starts
*
@@ -109,7 +109,7 @@ public class DriverInitRunner implements ApplicationRunner {
}
return;
} catch (Exception e) {
if (attempt == REGISTER_MAX_ATTEMPTS) {
if (attempt >= REGISTER_MAX_ATTEMPTS) {
log.error("Driver register failed after {} attempts, giving up", attempt, e);
throw e;
}
@@ -86,7 +86,7 @@ public final class DeviceMetadata extends AbstractMetadataCache<DeviceBO> {
if (value == null) {
// Manager has dropped this device; drop the orphan id so the Quartz read
// scan stops attempting to read a record that no longer exists.
if (driverMetadata.getDeviceIds().remove(id)) {
if (driverMetadata.removeDeviceId(id)) {
log.info("Drop orphan device id={} after upstream returned null", id);
}
}
@@ -114,7 +114,7 @@ public final class DeviceMetadata extends AbstractMetadataCache<DeviceBO> {
}
Map<Long, DriverAttributeConfigDTO> attributeConfigMap = device.getDriverAttributeConfigIdMap();
if (MapUtils.isEmpty(attributeConfigMap)
if (attributeConfigMap == null || attributeConfigMap.isEmpty()
|| !attributeConfigMap.keySet().containsAll(attributeMap.keySet())) {
log.warn("Driver config incomplete, deviceId={}, required={}, configured={}",
deviceId, attributeMap.keySet(),
@@ -200,7 +200,7 @@ public final class DeviceMetadata extends AbstractMetadataCache<DeviceBO> {
}
Map<Long, PointAttributeConfigDTO> attributeConfigMap = pointAttributeConfigMap.get(pointId);
if (MapUtils.isEmpty(attributeConfigMap)
if (attributeConfigMap == null || attributeConfigMap.isEmpty()
|| !attributeConfigMap.keySet().containsAll(attributeMap.keySet())) {
log.warn("Point config incomplete, deviceId={}, pointId={}, required={}, configured={}",
deviceId, pointId, attributeMap.keySet(),
@@ -288,7 +288,7 @@ public final class DeviceMetadata extends AbstractMetadataCache<DeviceBO> {
}
Map<Long, CommandAttributeConfigDTO> attributeConfigMap = commandAttributeConfigMap.get(commandId);
if (MapUtils.isEmpty(attributeConfigMap)
if (attributeConfigMap == null || attributeConfigMap.isEmpty()
|| !attributeConfigMap.keySet().containsAll(attributeMap.keySet())) {
log.warn("Command config incomplete, deviceId={}, commandId={}, required={}, configured={}",
deviceId, commandId, attributeMap.keySet(),
@@ -375,7 +375,7 @@ public final class DeviceMetadata extends AbstractMetadataCache<DeviceBO> {
}
Map<Long, EventAttributeConfigDTO> attributeConfigMap = eventAttributeConfigMap.get(eventId);
if (MapUtils.isEmpty(attributeConfigMap)
if (attributeConfigMap == null || attributeConfigMap.isEmpty()
|| !attributeConfigMap.keySet().containsAll(attributeMap.keySet())) {
log.warn("Event config incomplete, deviceId={}, eventId={}, required={}, configured={}",
deviceId, eventId, attributeMap.keySet(),
@@ -28,6 +28,7 @@ import lombok.Setter;
import lombok.ToString;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -59,6 +60,39 @@ public final class DriverMetadata {
* Identifiers of devices owned by the driver.
*/
private final Set<Long> deviceIds = ConcurrentHashMap.newKeySet();
/**
* Unmodifiable view of the device ids so callers cannot mutate the internal set
* through the getter. The underlying set is still live reads observe the most
* recent state. Use {@link #addDeviceId(Long)} / {@link #removeDeviceId(Long)} to
* mutate, or {@link #setDeviceIds(Set)} to replace the contents in place.
*
* @return unmodifiable live view of the device ids
*/
public Set<Long> getDeviceIds() {
return Collections.unmodifiableSet(deviceIds);
}
/**
* Add a device id to the live set.
*
* @param id device id to add
* @return {@code true} if the set did not already contain the id
*/
public boolean addDeviceId(Long id) {
return deviceIds.add(id);
}
/**
* Remove a device id from the live set.
*
* @param id device id to remove
* @return {@code true} if the set contained the id
*/
public boolean removeDeviceId(Long id) {
return deviceIds.remove(id);
}
/**
* Driver attributes keyed by attribute identifier.
*/
@@ -78,7 +78,10 @@ public class MetadataReceiver {
if (Objects.isNull(entityDTO) || Objects.isNull(entityDTO.getId())
|| Objects.isNull(entityDTO.getMetadataType())
|| Objects.isNull(entityDTO.getOperateType())) {
log.error("Invalid driver metadata: {}", entityDTO);
log.error("Invalid driver metadata: id={}, type={}, operate={}",
Objects.nonNull(entityDTO) ? entityDTO.getId() : null,
Objects.nonNull(entityDTO) ? entityDTO.getMetadataType() : null,
Objects.nonNull(entityDTO) ? entityDTO.getOperateType() : null);
RabbitAckUtil.reject(channel, deliveryTag);
return;
}
@@ -94,14 +97,14 @@ public class MetadataReceiver {
// Add the id first so a refresh that races with a Quartz scan does
// not bypass the just-loaded entry; loadCache below either fills
// the cache or, on a null upstream, removes the orphan id again.
driverMetadata.getDeviceIds().add(entityDTO.getId());
driverMetadata.addDeviceId(entityDTO.getId());
deviceMetadata.loadCache(entityDTO.getId());
} else if (MetadataOperateTypeEnum.DELETE.equals(entityDTO.getOperateType())) {
log.debug("Delete device: {}", entityDTO.getId());
// Remove the id before invalidating the cache so a Quartz scan
// hitting the cache between the two operations does not re-fetch
// the doomed device through the loader.
driverMetadata.getDeviceIds().remove(entityDTO.getId());
driverMetadata.removeDeviceId(entityDTO.getId());
deviceMetadata.removeCache(entityDTO.getId());
}
@@ -126,12 +126,12 @@ public final class TypedValueConverter {
}
case FLOAT -> {
float value = roundedFloat(linearValue(multiple, rawValue, base, "Point", pointType.getCode()),
rawValue, decimal, "Point", pointType.getCode());
rawValue, decimal, "Point");
yield new CalculatedPointValue(String.valueOf(value), (double) value);
}
case DOUBLE -> {
double value = roundedDouble(linearValue(multiple, rawValue, base, "Point", pointType.getCode()),
rawValue, decimal, "Point", pointType.getCode());
rawValue, decimal, "Point");
yield new CalculatedPointValue(String.valueOf(value), value);
}
case BOOLEAN -> {
@@ -162,9 +162,9 @@ public final class TypedValueConverter {
case SHORT -> (T) Short.valueOf(exactShort(decimal(value, type, label, typeCode), value, label, typeCode));
case INT -> (T) Integer.valueOf(exactInt(decimal(value, type, label, typeCode), value, label, typeCode));
case LONG -> (T) Long.valueOf(exactLong(decimal(value, type, label, typeCode), value, label, typeCode));
case FLOAT -> (T) Float.valueOf(finiteFloat(decimal(value, type, label, typeCode), value, label, typeCode));
case FLOAT -> (T) Float.valueOf(finiteFloat(decimal(value, type, label, typeCode), value, label));
case DOUBLE ->
(T) Double.valueOf(finiteDouble(decimal(value, type, label, typeCode), value, label, typeCode));
(T) Double.valueOf(finiteDouble(decimal(value, type, label, typeCode), value, label));
case BOOLEAN -> (T) Boolean.valueOf(strictBoolean(value, label, typeCode));
};
}
@@ -252,15 +252,15 @@ public final class TypedValueConverter {
return value.longValueExact();
}
private static float roundedFloat(BigDecimal value, String rawValue, byte decimal, String label, String typeCode) {
return ArithmeticUtil.round(finiteFloat(value, rawValue, label, typeCode), decimal);
private static float roundedFloat(BigDecimal value, String rawValue, byte decimal, String label) {
return ArithmeticUtil.round(finiteFloat(value, rawValue, label), decimal);
}
private static double roundedDouble(BigDecimal value, String rawValue, byte decimal, String label, String typeCode) {
return ArithmeticUtil.round(finiteDouble(value, rawValue, label, typeCode), decimal);
private static double roundedDouble(BigDecimal value, String rawValue, byte decimal, String label) {
return ArithmeticUtil.round(finiteDouble(value, rawValue, label), decimal);
}
private static float finiteFloat(BigDecimal value, String rawValue, String label, String typeCode) {
private static float finiteFloat(BigDecimal value, String rawValue, String label) {
float result = value.floatValue();
if (!Float.isFinite(result)) {
throw new OutRangeException("{} value out of float range: {} ~ {}, current: {}", label, -Float.MAX_VALUE,
@@ -269,7 +269,7 @@ public final class TypedValueConverter {
return result;
}
private static double finiteDouble(BigDecimal value, String rawValue, String label, String typeCode) {
private static double finiteDouble(BigDecimal value, String rawValue, String label) {
double result = value.doubleValue();
if (!Double.isFinite(result)) {
throw new OutRangeException("{} value out of double range: {} ~ {}, current: {}", label, -Double.MAX_VALUE,
@@ -129,7 +129,7 @@ class MetadataReceiverTest {
@Test
void deviceDeleteRemovesCacheAndDriverDeviceIds() throws Exception {
driverMetadata.getDeviceIds().add(99L);
driverMetadata.addDeviceId(99L);
MetadataEventDTO dto = event(MetadataTypeEnum.DEVICE, MetadataOperateTypeEnum.DELETE, 99L);
receiver.metadataReceive(channel, message, dto);
verify(deviceMetadata).removeCache(99L);
@@ -33,13 +33,23 @@ import org.springframework.stereotype.Component;
@Slf4j
@Component
@RequiredArgsConstructor
public class AuthenticGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
public class AuthenticGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthenticGatewayFilterFactory.Config> {
private final AuthenticGatewayFilter authenticGatewayFilter;
@Override
public GatewayFilter apply(Object config) {
public GatewayFilter apply(Config config) {
return authenticGatewayFilter;
}
/**
* Empty configuration marker for the authentic gateway filter.
* <p>
* The filter has no tunable options; this class exists to give the factory a
* dedicated, named config type instead of the overly generic {@code Object},
* which produces a confusing method signature.
*/
public static class Config {
}
}
@@ -77,6 +77,9 @@ public class McpGatewayController {
private final McpGatewayProperties mcpGatewayProperties;
private static Map<String, Object> orderedMap(Object... values) {
if (values.length % 2 != 0) {
throw new IllegalArgumentException("orderedMap requires key-value pairs (even number of arguments)");
}
Map<String, Object> map = new LinkedHashMap<>();
for (int i = 0; i < values.length; i += 2) {
map.put(String.valueOf(values[i]), values[i + 1]);
@@ -21,6 +21,7 @@ import jakarta.validation.constraints.NotEmpty;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
@@ -61,7 +62,7 @@ public class McpGatewayProperties {
if (StringUtils.isBlank(baseUrl)) {
throw new IllegalArgumentException("Unknown backend service: " + serviceName);
}
return StringUtils.removeEnd(baseUrl, "/");
return Strings.CS.removeEnd(baseUrl, "/");
}
}
@@ -44,12 +44,6 @@ public class ManagerInitRunner implements ApplicationRunner {
private final ScheduleForManagerService scheduleForManagerService;
/**
* Constructs a new ManagerInitRunner with the required service dependency.
*
* @param scheduleForManagerService The service responsible for manager scheduling
* operations
*/
/**
* Executes the initialization process when the application starts. This method
* initializes the schedule manager service to set up necessary scheduling
@@ -500,10 +500,10 @@ public class DashboardServiceImpl implements DashboardService {
// Others never represents "nothing is happening".
Map<Long, List<TopologyHiddenChildVO>> otherDevicesByDriver = new LinkedHashMap<>();
for (TopologyDeviceRow r : filteredDevices) {
Long deviceId = r.getId();
long deviceId = r.getId();
if (topDeviceIdSet.contains(deviceId))
continue;
Long drvId = r.getDriverId();
long drvId = r.getDriverId();
TopologyHiddenChildVO hidden = new TopologyHiddenChildVO();
hidden.setId("device:" + deviceId);
hidden.setName(r.getDeviceName());
@@ -541,15 +541,15 @@ public class DashboardServiceImpl implements DashboardService {
List<TopologyLinkVO> links = new ArrayList<>();
for (TopologyDriverRow r : topDrivers) {
Long id = r.getId();
long id = r.getId();
nodes.add(node("driver:" + id, driverNameById.get(id), 1, "driver", null));
}
// Driver Device links. Edge weight in cardinality mode = 1 (one
// relationship); in volume mode = that device's total pv count.
for (TopologyDeviceRow r : topDevices) {
Long id = r.getId();
Long driverId = r.getDriverId();
long id = r.getId();
long driverId = r.getDriverId();
String name = r.getDeviceName();
nodes.add(node("device:" + id, name, 2, "device", null));
long w = volumeMode ? nullZero(deviceWeight.get(id)) : 1L;
@@ -563,8 +563,16 @@ public class DashboardServiceImpl implements DashboardService {
if (volumeMode) {
long sum = 0;
for (TopologyHiddenChildVO c : children) {
long did = Long.parseLong(c.getId().substring("device:".length()));
sum += nullZero(deviceWeight.get(did));
String cid = c.getId();
if (cid == null || !cid.startsWith("device:")) {
continue;
}
try {
long did = Long.parseLong(cid.substring("device:".length()));
sum += nullZero(deviceWeight.get(did));
} catch (NumberFormatException ignored) {
// skip malformed device ids (shouldn't happen; ids are self-generated above)
}
}
w = Math.max(1L, sum);
} else {
@@ -629,7 +637,7 @@ public class DashboardServiceImpl implements DashboardService {
int keep = Math.min(TopologyLimits.TOP_POINTS_PER_PROFILE, allPoints.size());
for (int i = 0; i < keep; i++) {
TopologyPointRow r = allPoints.get(i);
Long id = r.getId();
long id = r.getId();
String name = r.getPointName();
nodes.add(node("point:" + id, name, 4, "point", null));
long w = volumeMode ? nullZero(pointWeight.get(id)) : 1L;
@@ -641,7 +649,7 @@ public class DashboardServiceImpl implements DashboardService {
long sumW = 0;
for (int i = keep; i < allPoints.size(); i++) {
TopologyPointRow r = allPoints.get(i);
Long id = r.getId();
long id = r.getId();
TopologyHiddenChildVO hidden = new TopologyHiddenChildVO();
hidden.setId("point:" + id);
hidden.setName(r.getPointName());
@@ -24,6 +24,7 @@ import io.github.pnoker.common.utils.MqttUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
@@ -59,11 +60,6 @@ public class MqttConfig {
private final MqttProperties mqttProperties;
/**
* Constructor for MQTT configuration
*
* @param mqttProperties MQTT configuration properties
*/
/**
* MQTT inbound message channel bean
*
@@ -147,7 +143,7 @@ public class MqttConfig {
private String prefixedTopicName(String topicName) {
String topicPrefix = mqttProperties.getTopicPrefix();
if (StringUtils.isBlank(topicPrefix) || StringUtils.startsWith(topicName, topicPrefix)) {
if (StringUtils.isBlank(topicPrefix) || Strings.CS.startsWith(topicName, topicPrefix)) {
return topicName;
}
return topicPrefix + topicName;
@@ -49,11 +49,6 @@ public class MqttInitRunner implements ApplicationRunner {
private final MqttScheduleService mqttScheduleService;
private final ObjectProvider<MqttReceiveService> mqttReceiveServiceProvider;
/**
* Creates a new MQTT initialization runner with the specified MQTT schedule service.
*
* @param mqttScheduleService The MQTT schedule service to be initialized
*/
/**
* Executes the MQTT initialization process when the application starts. This method
* is called automatically by Spring Boot after the application context is loaded.
@@ -19,6 +19,7 @@ package io.github.pnoker.common.mqtt.entity;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.MessageHeaders;
@@ -40,6 +41,7 @@ import java.util.UUID;
*/
@Getter
@Setter
@ToString
@Slf4j
public class MessageHeader implements Serializable {
@@ -27,6 +27,7 @@ import lombok.ToString;
import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
@@ -61,4 +62,24 @@ public class Pages implements Serializable {
@Schema(description = "Sort order items applied to the query results")
private List<OrderItem> orders = new ArrayList<>(2);
/**
* Unmodifiable view of the order items, so callers cannot mutate internal state
* through the getter. Use {@link #addOrder(OrderItem)} to append, or
* {@link #setOrders(List)} to replace.
*
* @return unmodifiable view of the order items
*/
public List<OrderItem> getOrders() {
return Collections.unmodifiableList(orders);
}
/**
* Append one order item to the internal list.
*
* @param order order item to append
*/
public void addOrder(OrderItem order) {
orders.add(order);
}
}
@@ -26,6 +26,10 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.Set;
import java.util.UUID;
/**
@@ -61,7 +65,7 @@ public class FileUtil {
Path dir = Paths.get(FolderConstant.TEMP_FILE_PATH, safePathSegments(segments));
if (Files.notExists(dir) || !Files.isDirectory(dir)) {
try {
Files.createDirectories(dir);
createSecureTempDirectories(dir);
} catch (IOException e) {
log.error("Failed to create temp directory: {}", dir, e);
}
@@ -69,6 +73,24 @@ public class FileUtil {
return dir.toString() + SymbolConstant.SLASH;
}
/**
* Create the temp directory chain with owner-only permissions (rwx------) to
* prevent information disclosure on multi-user hosts. Falls back to platform
* defaults on non-POSIX filesystems (e.g. Windows).
*
* @param dir the directory to create
* @throws IOException if directory creation fails
*/
private static void createSecureTempDirectories(Path dir) throws IOException {
FileAttribute<Set<PosixFilePermission>> ownerOnly =
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwx------"));
try {
Files.createDirectories(dir, ownerOnly);
} catch (UnsupportedOperationException ignored) {
Files.createDirectories(dir);
}
}
/**
* Create a module-scoped temporary upload file path.
*
@@ -25,6 +25,7 @@ import io.github.pnoker.common.entity.common.Pages;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
@@ -66,7 +67,7 @@ public class PageUtil {
}
page.setSize(pages.getSize());
List<OrderItem> orders = pages.getOrders();
List<OrderItem> orders = new ArrayList<>(pages.getOrders());
boolean anyMatch = orders.stream()
.filter(order -> Objects.nonNull(order) && StringUtils.isNotEmpty(order.getColumn()))
.anyMatch(order -> "create_time".equals(order.getColumn()));
@@ -21,6 +21,7 @@ import io.github.pnoker.common.constant.common.ExceptionConstant;
import io.github.pnoker.common.enums.PasswordAlgorithmEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@@ -82,7 +83,7 @@ public class PasswordUtil {
* @return password algorithm
*/
public static PasswordAlgorithmEnum algorithmOfHash(String hash) {
if (StringUtils.startsWith(hash, "$argon2")) {
if (Strings.CS.startsWith(hash, "$argon2")) {
return PasswordAlgorithmEnum.ARGON2ID;
}
return PasswordAlgorithmEnum.BCRYPT;
@@ -37,17 +37,17 @@ class PagesTest {
Pages pages = new Pages();
pages.setStartTime(100L);
pages.setEndTime(200L);
pages.getOrders().add(OrderItem.asc("name"));
pages.addOrder(OrderItem.asc("name"));
assertThat(pages.getStartTime()).isEqualTo(100L);
assertThat(pages.getEndTime()).isEqualTo(200L);
assertThat(pages.getOrders()).hasSize(1);
}
@Test
void ordersListIsMutableInitially() {
void addOrderAppendsToInternalList() {
Pages pages = new Pages();
assertThat(pages.getOrders()).isEmpty();
pages.getOrders().add(OrderItem.desc("create_time"));
pages.addOrder(OrderItem.desc("create_time"));
assertThat(pages.getOrders()).extracting(OrderItem::getColumn).contains("create_time");
}
}
@@ -65,7 +65,7 @@ class PageUtilTest {
@Test
void pagePreservesUserOrdersAndAddsCreateTime() {
Pages pages = new Pages();
pages.getOrders().add(OrderItem.asc("name"));
pages.addOrder(OrderItem.asc("name"));
Page<Object> page = PageUtil.page(pages);
assertThat(page.orders()).extracting(OrderItem::getColumn)
.contains("name", "create_time");
@@ -74,7 +74,7 @@ class PageUtilTest {
@Test
void pageDoesNotDuplicateExistingCreateTimeOrder() {
Pages pages = new Pages();
pages.getOrders().add(OrderItem.desc("create_time"));
pages.addOrder(OrderItem.desc("create_time"));
Page<Object> page = PageUtil.page(pages);
long createTimeOrders = page.orders().stream()
.filter(it -> "create_time".equals(it.getColumn()))
@@ -54,16 +54,6 @@ public class ResourceRegistrar {
private final Environment environment;
/**
* Create a registrar that scans the current service and submits the inventory through
* the active facade implementation.
*
* @param scanner endpoint scanner for the local WebFlux mappings
* @param facade transport-neutral resource registry facade
* @param properties registrar runtime options
* @param environment Spring environment used for service-name fallback
*/
/**
* Register scanned endpoints after the application is ready and all WebFlux handler
* mappings have been built. Failures abort startup only when
@@ -69,14 +69,6 @@ public class ApiEndpointScanner {
private final AntPathMatcher matcher = new AntPathMatcher();
/**
* Create a scanner bound to the WebFlux handler mapping built by the current
* application context.
*
* @param handlerMapping WebFlux request mapping registry
* @param properties registrar scan configuration
*/
private static String buildApiName(HandlerMethod handler, String httpMethod, boolean isSingleGet) {
String protectedApiName = resolvePreAuthorizeApiName(handler);
if (Objects.nonNull(protectedApiName)) {
@@ -181,9 +181,6 @@ public abstract class AbstractJdbcDriverCustomService implements DriverCustomSer
*/
protected HikariDataSource getConnector(Long deviceId, Map<String, AttributeBO> driverConfig) {
return connectMap.computeIfAbsent(deviceId, id -> {
String host = getConfigValue(driverConfig, "host", "localhost");
int port = getConfigIntValue(driverConfig, "port", getDefaultPort());
String database = getRequiredConfig(driverConfig, "database");
String username = getConfigValue(driverConfig, "username", "root");
String password = getConfigValue(driverConfig, "password", "");
int queryTimeout = getConfigIntValue(driverConfig, "queryTimeout", 30);
@@ -217,18 +217,9 @@ public class BacnetIpDriverCustomServiceImpl implements DriverCustomService {
}
if (upperName.startsWith("MULTI_STATE_") || upperName.startsWith("DEVICE")) {
try {
try {
return new UnsignedInteger(Integer.parseInt(value));
} catch (NumberFormatException e) {
return new UnsignedInteger(0);
}
} catch (NumberFormatException ignored) {
log.warn("BACnet write: multi-state/device value '{}' is not an integer, falling back to Real", value);
return new Real(Float.parseFloat(value));
return new UnsignedInteger(Integer.parseInt(value));
} catch (NumberFormatException e) {
return new UnsignedInteger(0);
}
}
// Analog types and fallback
@@ -26,7 +26,6 @@ import io.github.pnoker.common.driver.entity.bo.DeviceBO;
import io.github.pnoker.common.driver.entity.bo.PointBO;
import io.github.pnoker.common.driver.metadata.DriverMetadata;
import io.github.pnoker.common.driver.service.DriverCustomService;
import io.github.pnoker.common.driver.service.DriverSenderService;
import io.github.pnoker.common.entity.dto.MetadataEventDTO;
import io.github.pnoker.common.enums.MetadataOperateTypeEnum;
import io.github.pnoker.common.enums.MetadataTypeEnum;
@@ -79,17 +78,14 @@ public class CanDriverCustomServiceImpl implements DriverCustomService {
java.util.regex.Pattern.compile("^[A-Za-z0-9_]+$");
private final DriverMetadata driverMetadata;
private final DriverSenderService driverSenderService;
@Value("${dc3.driver.code}")
private String driverCode;
private Map<Long, Boolean> deviceMap;
public CanDriverCustomServiceImpl(DriverMetadata driverMetadata,
DriverSenderService driverSenderService) {
public CanDriverCustomServiceImpl(DriverMetadata driverMetadata) {
this.driverMetadata = driverMetadata;
this.driverSenderService = driverSenderService;
}
private static void checkRequired(Map<String, AttributeBO> config, String code,
@@ -21,7 +21,6 @@ import io.github.pnoker.common.driver.entity.bean.ValidationReport;
import io.github.pnoker.common.driver.entity.bo.AttributeBO;
import io.github.pnoker.common.driver.entity.bo.PointBO;
import io.github.pnoker.common.driver.metadata.DriverMetadata;
import io.github.pnoker.common.driver.service.DriverSenderService;
import io.github.pnoker.common.enums.AttributeTypeEnum;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -46,14 +45,11 @@ class CanDriverCustomServiceImplTest {
@Mock
private DriverMetadata driverMetadata;
@Mock
private DriverSenderService driverSenderService;
private CanDriverCustomServiceImpl service;
@BeforeEach
void setUp() {
service = new CanDriverCustomServiceImpl(driverMetadata, driverSenderService);
service = new CanDriverCustomServiceImpl(driverMetadata);
}
@Test
@@ -312,11 +312,23 @@ public class EthernetIpDriverCustomServiceImpl implements DriverCustomService {
}
case "SINT" -> {
buf = ByteBuffer.allocate(1);
buf.put(Byte.parseByte(value));
byte b;
try {
b = Byte.parseByte(value);
} catch (NumberFormatException e) {
b = 0;
}
buf.put(b);
}
case "INT" -> {
buf = ByteBuffer.allocate(2);
buf.order(ByteOrder.LITTLE_ENDIAN).putShort(Short.parseShort(value));
short s;
try {
s = Short.parseShort(value);
} catch (NumberFormatException e) {
s = 0;
}
buf.order(ByteOrder.LITTLE_ENDIAN).putShort(s);
}
case "DINT" -> {
buf = ByteBuffer.allocate(4);
@@ -332,7 +344,13 @@ public class EthernetIpDriverCustomServiceImpl implements DriverCustomService {
}
case "REAL" -> {
buf = ByteBuffer.allocate(4);
buf.order(ByteOrder.LITTLE_ENDIAN).putFloat(Float.parseFloat(value));
float f;
try {
f = Float.parseFloat(value);
} catch (NumberFormatException e) {
f = 0f;
}
buf.order(ByteOrder.LITTLE_ENDIAN).putFloat(f);
}
default -> buf = ByteBuffer.wrap(value.getBytes(StandardCharsets.US_ASCII));
}
@@ -444,7 +444,12 @@ public class FinsDriverCustomServiceImpl implements DriverCustomService {
case "INT16":
case "UINT16": {
data = new byte[2];
short s = Short.parseShort(value);
short s;
try {
s = Short.parseShort(value);
} catch (NumberFormatException e) {
s = 0;
}
ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN).putShort(s);
break;
}
@@ -467,7 +472,12 @@ public class FinsDriverCustomServiceImpl implements DriverCustomService {
}
case "FLOAT": {
data = new byte[4];
float f = Float.parseFloat(value);
float f;
try {
f = Float.parseFloat(value);
} catch (NumberFormatException e) {
f = 0f;
}
ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN).putFloat(f);
break;
}
@@ -147,7 +147,7 @@ class MqttReceiveServiceImplTest {
@Test
void receiveEventMessageMatchesConfiguredTopicAndReportsEvent() {
driverMetadata.getDeviceIds().add(10L);
driverMetadata.addDeviceId(10L);
driverMetadata.setEventAttributeIdMap(Map.of(
1L, eventAttribute(1L, "sourceTopic"),
2L, eventAttribute(2L, "eventCodePath"),
@@ -192,7 +192,7 @@ class MqttReceiveServiceImplTest {
@Test
void eventMessageWithPointIdentityReportsBothEventAndPointValue() {
driverMetadata.getDeviceIds().add(10L);
driverMetadata.addDeviceId(10L);
driverMetadata.setEventAttributeIdMap(Map.of(
1L, eventAttribute(1L, "sourceTopic"),
2L, eventAttribute(2L, "eventCodePath"),
@@ -233,7 +233,7 @@ class MqttReceiveServiceImplTest {
@Test
void eventReportFailureDoesNotDropPointValue() {
driverMetadata.getDeviceIds().add(10L);
driverMetadata.addDeviceId(10L);
driverMetadata.setEventAttributeIdMap(Map.of(
1L, eventAttribute(1L, "sourceTopic"),
2L, eventAttribute(2L, "eventCodePath"),
@@ -175,8 +175,7 @@ public class Sl651DriverCustomServiceImpl implements DriverCustomService {
Object listener = Proxy.newProxyInstance(listenerClass.getClassLoader(), new Class<?>[]{listenerClass},
(proxy, method, args) -> {
if ("onMessage".equals(method.getName()) && Objects.nonNull(args) && args.length == 3) {
byte[] messageBytes = args[0] instanceof byte[] bytes ? bytes : new byte[0];
handleSl651Message(messageBytes, args[1], args[2]);
handleSl651Message(args[1], args[2]);
}
return null;
});
@@ -268,7 +267,7 @@ public class Sl651DriverCustomServiceImpl implements DriverCustomService {
}
}
private void handleSl651Message(byte[] bytes, Object response, Object bodyResponses) {
private void handleSl651Message(Object response, Object bodyResponses) {
String stationAddr = bytesToHex(invokeBytes(response, "getRemoteStationAddress"));
String funcCode = bytesToHex(invokeBytes(response, "getFunctionCode"));
List<String> elements = extractBodyElements(bodyResponses);
@@ -63,7 +63,7 @@ class Sl651DriverCustomServiceImplTest {
@Test
void forwardTelemetryMapsStationElementsToConfiguredPoints() {
driverMetadata.getDeviceIds().add(10L);
driverMetadata.addDeviceId(10L);
DeviceBO device = new DeviceBO();
device.setId(10L);
device.setDeviceCode("01020304");
@@ -114,7 +114,7 @@ public class SnmpDriverCustomServiceImpl implements DriverCustomService {
if (clientMap.containsKey(device.getId())) {
return DeviceHealthState.online();
}
Snmp snmp = getConnector(device.getId(), driverConfig);
Snmp snmp = getConnector(device.getId());
return Objects.nonNull(snmp) ? DeviceHealthState.online() : DeviceHealthState.offline();
}
@@ -148,10 +148,10 @@ public class SnmpDriverCustomServiceImpl implements DriverCustomService {
@Override
public ReadPointValue read(Map<String, AttributeBO> driverConfig, Map<String, AttributeBO> pointConfig,
DeviceBO device, PointBO point) {
Snmp snmp = getConnector(device.getId(), driverConfig);
Snmp snmp = getConnector(device.getId());
String oid = getConfigValue(pointConfig, "oid", "");
try {
CommunityTarget target = buildTarget(device.getId(), driverConfig);
CommunityTarget target = buildTarget(driverConfig);
PDU pdu = new PDU();
pdu.add(new VariableBinding(new OID(oid)));
@@ -177,11 +177,11 @@ public class SnmpDriverCustomServiceImpl implements DriverCustomService {
@Override
public Boolean write(Map<String, AttributeBO> driverConfig, Map<String, AttributeBO> pointConfig,
DeviceBO device, PointBO point, WritePointValue writePointValue) {
Snmp snmp = getConnector(device.getId(), driverConfig);
Snmp snmp = getConnector(device.getId());
String oid = getConfigValue(pointConfig, "oid", "");
String snmpType = getConfigValue(pointConfig, "snmpType", "OCTET_STRING");
try {
CommunityTarget target = buildTarget(device.getId(), driverConfig);
CommunityTarget target = buildTarget(driverConfig);
PDU pdu = new PDU();
Variable variable = createVariable(snmpType, writePointValue.getValue(String.class));
@@ -205,11 +205,10 @@ public class SnmpDriverCustomServiceImpl implements DriverCustomService {
/**
* Get or create an SNMP client for the given device.
*
* @param deviceId unique device identifier
* @param driverConfig driver configuration
* @param deviceId unique device identifier
* @return cached or newly created Snmp instance
*/
private Snmp getConnector(Long deviceId, Map<String, AttributeBO> driverConfig) {
private Snmp getConnector(Long deviceId) {
return clientMap.computeIfAbsent(deviceId, id -> {
try {
TransportMapping<org.snmp4j.smi.UdpAddress> transport = new DefaultUdpTransportMapping();
@@ -228,11 +227,10 @@ public class SnmpDriverCustomServiceImpl implements DriverCustomService {
/**
* Build an SNMP community target from driver configuration.
*
* @param deviceId device identifier
* @param driverConfig driver configuration
* @return configured CommunityTarget
*/
private CommunityTarget buildTarget(Long deviceId, Map<String, AttributeBO> driverConfig) {
private CommunityTarget buildTarget(Map<String, AttributeBO> driverConfig) {
String host = getConfigValue(driverConfig, "host", "127.0.0.1");
int port = getConfigIntValue(driverConfig, "port", 161);
String version = getConfigValue(driverConfig, "version", "v2c");
+1 -1
View File
@@ -48,5 +48,5 @@ export function isNull(val: unknown): boolean {
if (val !== null && typeof val === 'object') {
return Object.keys(val as object).length === 0;
}
return val === 'null' || val == null || val === 'undefined' || val === undefined || val === '';
return val === 'null' || val == null || val === 'undefined' || val === '';
}