refactor: update to Springboot4 and Hibernate 7 (#4057)

Co-authored-by: lynx009 <2030509072@qq.com>
This commit is contained in:
Logic
2026-03-09 23:34:15 +08:00
committed by GitHub
co-authored by lynx009
parent 00b83ff531
commit 9e1a460dc7
166 changed files with 1343 additions and 8609 deletions
+2 -2
View File
@@ -3,5 +3,5 @@ FROM gitpod/workspace-full
USER gitpod
RUN bash -c ". /home/gitpod/.sdkman/bin/sdkman-init.sh && \
sdk install java 17.0.3-ms && \
sdk default java 17.0.3-ms"
sdk install java 21.0.2-tem && \
sdk default java 21.0.2-tem"
+2 -2
View File
@@ -32,7 +32,7 @@ Even small corrections to typos are very welcome :)
#### Backend start
1. Requires `maven3+`, `java17` and `lombok` environments
1. Requires `maven3+`, `java21` and `lombok` environments
2. (Optional) Modify the configuration file: `hertzbeat-startup/src/main/resources/application.yml`
@@ -172,7 +172,7 @@ Add WeChat account `ahertzbeat` to pull you into the WeChat group.
#### 后端启动
1. 需要 `maven3+`, `java17` 和 `lombok` 环境
1. 需要 `maven3+`, `java21` 和 `lombok` 环境
2. (可选)修改配置文件配置信息-`hertzbeat-startup/src/main/resources/application.yml`
+1 -1
View File
@@ -156,7 +156,7 @@ Detailed config refer to [Install HertzBeat via Package](https://hertzbeat.apach
##### 3Start via source code
1. Local source code debugging needs to start the back-end project `hertzbeat-startup` and the front-end project `web-app`.
2. Backendneed `maven3+`, `java17`, `lombok`, add VM options in IDE: ` --add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED `, then start the `hertzbeat-startup` service.
2. Backendneed `maven3+`, `java21`, `lombok`, add VM options in IDE: ` --add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED `, then start the `hertzbeat-startup` service.
3. Webneed `nodejs npm angular-cli` environment, Run `ng serve --open` in `web-app` directory after backend startup.
4. Access `http://localhost:4200` to start, default account: `admin/hertzbeat`
+1 -1
View File
@@ -153,7 +153,7 @@
##### 方式三:本地代码启动
1. 此为前后端分离项目,本地代码调试需要分别启动后端工程 `hertzbeat-startup` 和前端工程 `web-app`
2. 后端:需要 `maven3+`, `java17` 和 `lombok` 环境,修改 `YML` 配置信息,添加JVM参数`--add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED`后启动 `hertzbeat-startup` 服务即可。
2. 后端:需要 `maven3+`, `java21` 和 `lombok` 环境,修改 `YML` 配置信息,添加JVM参数`--add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED`后启动 `hertzbeat-startup` 服务即可。
3. 前端:需要 `nodejs npm angular-cli`环境,待本地后端启动后,在 `web-app` 目录下启动 `ng serve --open`
4. 浏览器访问 `http://localhost:4200` 即可开始,默认账号密码 `admin/hertzbeat`
+15 -15
View File
@@ -7,7 +7,7 @@
<p align="center">
<b>Readme</b>:
<a href="README.md">English</a> | <a href="README_CN.md">中文</a> | <b><a href="README_JP.md">日本語</a></b>
</p>
</p>
[![Discord](https://img.shields.io/badge/Chat-Discord-7289DA?logo=discord)](https://discord.gg/Fb6M73htGr)
[![Reddit](https://img.shields.io/badge/Reddit-Community-7289DA?logo=reddit)](https://www.reddit.com/r/hertzbeat/)
@@ -20,13 +20,13 @@
[![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod&color=green)](https://gitpod.io/#https://github.com/apache/hertzbeat)
**公式サイト: [hertzbeat.apache.org](https://hertzbeat.apache.org)**
**公式サイト: [hertzbeat.apache.org](https://hertzbeat.apache.org)**
**メール:** メーリングリストに登録するために、<a href="mailto:dev-subscribe@hertzbeat.apache.org">dev-subscribe@hertzbeat.apache.org</a>にメールを送ってください。
## 🎡 <font color="green">紹介</font>
[Apache HertzBeat™](https://github.com/apache/hertzbeat) は AI 駆動の次世代オープンソースリアルタイム観測システムです。メトリクスとログの統一収集、アラートの一元配信、インテリジェント管理分析。エージェント不要、高性能クラスタ、強力なカスタム監視とステータスページ構築機能を提供します。
[Apache HertzBeat™](https://github.com/apache/hertzbeat) は AI 駆動の次世代オープンソースリアルタイム観測システムです。メトリクスとログの統一収集、アラートの一元配信、インテリジェント管理分析。エージェント不要、高性能クラスタ、強力なカスタム監視とステータスページ構築機能を提供します。
### 特性
@@ -39,19 +39,19 @@
- 高性能で、コレクタークラスタの水平拡張、マルチ分離ネットワーク監視、クラウドエッジ協調をサポート。
- 強力なステータスページ構築機能を提供し、サービスのリアルタイムステータスをユーザーに簡単に伝達。
> `HertzBeat`の統一プラットフォーム、AI インテリジェンス、強力なカスタマイズ、多種類サポート、高性能、容易な拡張性は、ユーザーが迅速かつ便利に観測要件を実現することを支援することを目的としています。
> `HertzBeat`の統一プラットフォーム、AI インテリジェンス、強力なカスタマイズ、多種類サポート、高性能、容易な拡張性は、ユーザーが迅速かつ便利に観測要件を実現することを支援することを目的としています。
----
----
## 🥐 モジュール
## 🥐 モジュール
![hertzBeat](home/static/img/docs/hertzbeat-arch.png)
![hertzBeat](home/static/img/docs/hertzbeat-arch.png)
## ⛄ 利用可能
## ⛄ 利用可能
> `mysql`、`Java仮想マシン`、`k8s` などのすべての監視コレクション タイプを `YML` 監視テンプレートとして定義し、ユーザーがそれらをインポートして対応するタイプの監視をサポートできるようにしています。
> `mysql`、`Java仮想マシン`、`k8s` などのすべての監視コレクション タイプを `YML` 監視テンプレートとして定義し、ユーザーがそれらをインポートして対応するタイプの監視をサポートできるようにしています。
> カスタマイズされた一般的なモニターのテンプレートを提供することを歓迎します。
- [Website](https://raw.githubusercontent.com/apache/hertzbeat/master/hertzbeat-manager/src/main/resources/define/app-website.yml), [Port Telnet](https://raw.githubusercontent.com/apache/hertzbeat/master/hertzbeat-manager/src/main/resources/define/app-port.yml),
@@ -93,7 +93,7 @@
- その他、カスタマイズされたモニターテンプレート。
- 通知利用可能 `Discord` `Slack` `Telegram` `Email` `Dingtalk` `WeChat` `FeiShu` `Webhook` `SMS` `ServerChan`
## 🐕 クイックスタート
## 🐕 クイックスタート
- HertzBeat をローカルに展開する場合は、以下のデプロイメントドキュメントを参照してください。
@@ -102,7 +102,7 @@
##### 方式1:Docker
1. `docker` で以下の指令を実行します:
1. `docker` で以下の指令を実行します:
```shell
docker run -d -p 1157:1157 -p 1158:1158 --name hertzbeat apache/hertzbeat
@@ -156,7 +156,7 @@
##### 方式3:ローカルの実行
1. ローカルの実行には、バックエンドのプロジェクト`hertzbeat-startup`とフロントエンドのプロジェクト`web-app`を起動する必要があります。
2. バックエンド:`maven3+`、`java17`と`lombok`の環境は必要です。`YML` 設定を修正し、Java仮想マシンパラメータに`--add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED` を追加し、`hertzbeat-startup` を起動します。
2. バックエンド:`maven3+`、`Java21`と`lombok`の環境は必要です。`YML` 設定を修正し、Java仮想マシンパラメータに`--add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED` を追加し、`hertzbeat-startup` を起動します。
3. フロントエンド:`nodejs npm angular-cli`の環境は必要です。ローカルのバックエンドが立ち上がったら、`web-app` ディレクトリで `ng serve --open` というコマンドを実行します。
4. スタート:`http://localhost:4200`にアクセスします。デフォルトのアカウントとパスワード:`admin/hertzbeat`。
@@ -166,7 +166,7 @@
[Docker-Compose 部署脚本](script/docker-compose)でpostgresql/mysqlデータベース、victoria-metrics、iotdb、またはtdengine時系列データベースとHertzbeat一括デプロイ。
詳細ステップ [通过 Docker-Compose 安装 HertzBeat](script/docker-compose/README.md)
詳細ステップ [通过 Docker-Compose 安装 HertzBeat](script/docker-compose/README.md)
##### 方式5:Kubernetes Helm Charts
@@ -178,8 +178,8 @@ Helm ChartでHertzBeatクラスタコンポーネントをKubernetesクラスタ
## ✨ Contributors
Thanks these wonderful people, welcome to join us:
[貢献ガイド](CONTRIBUTING.md)
Thanks these wonderful people, welcome to join us:
[貢献ガイド](CONTRIBUTING.md)
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
@@ -557,7 +557,7 @@ Thanks these wonderful people, welcome to join us:
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
## 💬 コミュニティ交流
+2 -5
View File
@@ -27,7 +27,7 @@
<version>${hertzbeat.version}</version>
<properties>
<spring-ai.version>1.1.1</spring-ai.version>
<java.version>17</java.version>
<java.version>21</java.version>
</properties>
<dependencies>
@@ -78,10 +78,7 @@
<groupId>com.usthe.sureness</groupId>
<artifactId>spring-boot3-starter-sureness</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
@@ -41,7 +41,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import javax.validation.Valid;
import jakarta.validation.Valid;
import java.util.List;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
@@ -26,7 +26,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import jakarta.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -92,12 +92,12 @@ public class SkillRegistry implements ToolCallbackProvider {
@Override
public ToolCallback[] getToolCallbacks() {
List<ToolCallback> callbacks = new ArrayList<>();
for (SopDefinition skill : skillMap.values()) {
log.debug("Registering SOP as tool: {}", skill.getName());
callbacks.add(new SopToolCallback(skill, sopEngine));
}
return callbacks.toArray(new ToolCallback[0]);
}
}
@@ -17,8 +17,7 @@
package org.apache.hertzbeat.ai.sop.registry;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import tools.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.ai.sop.model.SopDefinition;
import org.springframework.core.io.Resource;
@@ -28,6 +27,7 @@ import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import tools.jackson.dataformat.yaml.YAMLFactory;
/**
* Loader for AI SOP definitions from YAML files.
@@ -50,11 +50,11 @@ public class SopYamlLoader {
public List<SopDefinition> loadAllSkills() {
List<SopDefinition> skills = new ArrayList<>();
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try {
Resource[] resources = resolver.getResources(SKILLS_PATH_PATTERN);
log.info("Found {} SOP definition files", resources.length);
for (Resource resource : resources) {
try {
SopDefinition definition = yamlMapper.readValue(resource.getInputStream(), SopDefinition.class);
@@ -69,7 +69,7 @@ public class SopYamlLoader {
} catch (IOException e) {
log.error("Failed to scan for SOP definition files: {}", e.getMessage());
}
return skills;
}
}
@@ -17,9 +17,9 @@
package org.apache.hertzbeat.ai.tools.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.node.ArrayNode;
import tools.jackson.databind.node.ObjectNode;
import com.usthe.sureness.subject.SubjectSum;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.ai.config.McpContextHolder;
@@ -74,30 +74,30 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
8. based on the user's output, conditionally call the bind_monitors_to_alert_rule tool to bind monitors to the alert rule
VERY VERY IMPORTANT:
- ALWAYS USE the value field from the get_apps_metrics_hierarchy's json response when creating alert expressions on the field parameters
EXAMPLES FOR FIELD CONDITION EXPRESSION( Do not copy these examples, they are just for reference ):
These are all just examples, you can take inspiration from them and create a rule based on hierarchy, always ask the user for all params, do not assume them, even for these examples:
1. Kafka JVM Alert:
- App: "kafka", Metric: "jvm_basic"
- Field condition: equals(VmName, "myVM")
- Field condition expression: equals(VmName, "myVM")
2. LLM Credits Alert:
- App: "openai", Metric: "credit_grants"
- Field condition: total_granted > some_value
- Field condition expression: total_granted > 1000
3. HBase Master Alert:
- App: "hbase_master", Metric: "server"
- Field condition: heap_memory_used > 80 or some_factor<100
- Field condition expression: heap_memory_used > 80 or some_factor<100
4. Complex OpenAI Credits Alert:
- App: "openai", Metric: "credit_grants"
- Field condition: total_used > 123 and total_granted > 333 and (total_granted > 3444 and total_paid_available < 5556)
- Field condition expression: total_used > 123 and total_granted > 333 and (total_granted > 3444 and total_paid_available < 5556)
FIELD CONDITIONS GUIDANCE:
- Field names come from metric's children in hierarchy (leaf nodes)
- Use the "value" field from the metric's children, not the label when creating conditions
@@ -109,7 +109,7 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
- String values should be quoted: equals(VmName, "my-vm")
- Simple conditions: heap_memory_used > 80, total_granted <= 1000
- Complex conditions: total_used > 123 and total_granted > 333 and (total_granted > 3444 and total_paid_available < 5556)
PRIORITY LEVELS:
- 0: Critical (immediate action required)
- 1: Warning (attention needed, default)
@@ -196,7 +196,7 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
return expressionValidation; // Return expression validation error message
}
String expr = String.format("equals(__app__,\"%s\") && equals(__metrics__,\"%s\") && %s",
String expr = String.format("equals(__app__,\"%s\") && equals(__metrics__,\"%s\") && %s",
app.trim(), metrics.trim(), fieldConditions.trim());
@@ -242,7 +242,7 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
.datasource(datasource)
.enable(enable)
.build();
log.debug("Current security subject for addAlertDefine: {}", subjectSum);
alertDefineService.addAlertDefine(alertDefine);
@@ -251,9 +251,9 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
String bindingNote = String.format(" (Use bind_monitors_to_alert_rule tool to associate specific monitors)");
log.info("Successfully created alert rule '{}' with ID: {}", name, alertDefine.getId());
StringBuilder response = new StringBuilder();
response.append(String.format("Successfully created %s alert rule '%s' with ID: %d\n",
response.append(String.format("Successfully created %s alert rule '%s' with ID: %d\n",
type, name, alertDefine.getId()));
response.append(String.format("Expression: %s\n", expr));
response.append(String.format("Priority: %d (%s)\n", priority, severityLabel));
@@ -442,7 +442,7 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
HertzBeat: Get the hierarchical structure of all available apps and their metrics for alert rule creation.
This tool provides the exact app name, metric name and corresponding param names according to each metric.
Returns structured JSON data showing the complete hierarchy with field parameters for alert expressions.
JSON Structure:
- app: The application name
- description: Tool description
@@ -452,7 +452,7 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
- Non-leaf nodes have: children array
VERY IMPORTANT:
- ALWAYS USE the value field from the field parameters when creating alert expressions.
This structured data is needed to create proper alert expressions.
""")
public String getAppsMetricsHierarchy(
@@ -524,7 +524,7 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
// Parse monitor IDs from comma-separated string
String[] monitorIdArray = monitorIds.split(",");
List<String> validMonitorIds = new ArrayList<>();
for (String monitorId : monitorIdArray) {
String trimmedId = monitorId.trim();
if (!trimmedId.isEmpty()) {
@@ -536,7 +536,7 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
}
}
}
if (validMonitorIds.isEmpty()) {
return "Error: No valid monitor IDs provided";
}
@@ -560,19 +560,19 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
// Get the current expression and modify it
String currentExpr = existingRule.getExpr();
String newExpr;
// Check if the expression already has __instance__ conditions
if (currentExpr.contains("__instance__")) {
// Extract existing monitor IDs and merge with new ones
List<String> existingMonitorIds = UtilityClass.extractExistingMonitorIds(currentExpr);
// Add new monitor IDs that aren't already present
for (String newId : validMonitorIds) {
if (!existingMonitorIds.contains(newId)) {
existingMonitorIds.add(newId);
}
}
String updatedMonitorCondition;
if (existingMonitorIds.size() == 1) {
updatedMonitorCondition = String.format("equals(__instance__, \"%s\")", existingMonitorIds.get(0));
@@ -587,31 +587,31 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
conditionBuilder.append(")");
updatedMonitorCondition = conditionBuilder.toString();
}
// Replace existing __instance__ conditions with updated ones
newExpr = UtilityClass.replaceInstanceConditions(currentExpr, updatedMonitorCondition);
// Update the alert rule
existingRule.setExpr(newExpr);
log.debug("Current security subject for modifyAlertDefine: {}", subjectSum);
alertDefineService.modifyAlertDefine(existingRule);
log.info("Successfully added monitors {} to existing bindings for alert rule ID: {}", validMonitorIds, ruleId);
return String.format("Successfully added %d new monitor(s) to alert rule ID %d.\nTotal bound monitors: %s\nUpdated expression: %s",
return String.format("Successfully added %d new monitor(s) to alert rule ID %d.\nTotal bound monitors: %s\nUpdated expression: %s",
validMonitorIds.size(), ruleId, String.join(", ", existingMonitorIds), newExpr);
}
// Insert the monitor condition after the metrics condition
// Pattern: equals(__app__,"app") && equals(__metrics__,"metric") && [existing_conditions]
// Result: equals(__app__,"app") && equals(__metrics__,"metric") && [monitor_condition] && [existing_conditions]
if (currentExpr.matches(".*equals\\(__app__,\"[^\"]+\"\\)\\s*&&\\s*equals\\(__metrics__,\"[^\"]+\"\\)\\s*&&\\s*.*")) {
// Find the position after the metrics condition
String metricsPattern = "equals\\(__metrics__,\"[^\"]+\"\\)";
java.util.regex.Pattern regex = java.util.regex.Pattern.compile(metricsPattern);
java.util.regex.Matcher matcher = regex.matcher(currentExpr);
if (matcher.find()) {
int metricsEnd = matcher.end();
// Find the " && " after the metrics condition
@@ -636,7 +636,7 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
alertDefineService.modifyAlertDefine(existingRule);
log.info("Successfully bound monitors {} to alert rule ID: {}", validMonitorIds, ruleId);
return String.format("Successfully bound %d monitor(s) to alert rule ID %d.\nMonitor IDs: %s\nUpdated expression: %s",
return String.format("Successfully bound %d monitor(s) to alert rule ID %d.\nMonitor IDs: %s\nUpdated expression: %s",
validMonitorIds.size(), ruleId, String.join(", ", validMonitorIds), newExpr);
} catch (Exception e) {
@@ -17,9 +17,8 @@
package org.apache.hertzbeat.ai.utils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import tools.jackson.databind.node.ArrayNode;
import tools.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import java.time.Instant;
@@ -31,6 +30,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.hertzbeat.manager.pojo.dto.Hierarchy;
import tools.jackson.databind.ObjectMapper;
/**
* Utility class providing helper methods for field expression validation, parsing,
@@ -41,13 +41,13 @@ import org.apache.hertzbeat.manager.pojo.dto.Hierarchy;
@Slf4j
@lombok.experimental.UtilityClass
public class UtilityClass {
/**
* Validates the syntax of field conditions expression
* @param fieldConditions Field conditions string to validate
* @return "VALID" if syntax is correct, error message otherwise
*/
public String validateExpressionSyntax(String fieldConditions) {
try {
log.debug("Validating expression syntax: {}", fieldConditions);
@@ -414,7 +414,7 @@ public class UtilityClass {
return node;
}
/**
* Format timestamp to readable format
*/
+5
View File
@@ -35,6 +35,11 @@
<artifactId>hertzbeat-base</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat-common-core</artifactId>
<scope>provided</scope>
</dependency>
<!-- plugin -->
<dependency>
<groupId>org.apache.hertzbeat</groupId>
@@ -18,6 +18,8 @@
package org.apache.hertzbeat.alert.calculate.realtime;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.calculate.realtime.window.LogWorker;
import org.apache.hertzbeat.alert.calculate.realtime.window.TimeService;
@@ -28,8 +30,6 @@ import org.apache.hertzbeat.common.util.BackoffUtils;
import org.apache.hertzbeat.common.util.ExponentialBackoff;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
@@ -83,29 +83,29 @@ public class WindowedLogRealTimeAlertCalculator implements Runnable {
}
}
}
private void processLogEntry(LogEntry logEntry) {
// Extract event timestamp
long eventTimestamp = extractEventTimestamp(logEntry);
// Check if timestamp is within reasonable range
if (!timeService.isValidTimestamp(eventTimestamp)) {
log.warn("Dropping log with invalid timestamp: {}", eventTimestamp);
return;
}
// Check if this is late data based on current watermark
if (timeService.isLateData(eventTimestamp)) {
log.warn("Dropping late data, timestamp: {}, watermark: {}",
log.warn("Dropping late data, timestamp: {}, watermark: {}",
eventTimestamp, timeService.getCurrentWatermark());
return;
}
// Update max timestamp (only validated timestamps can update watermark)
timeService.updateMaxTimestamp(eventTimestamp);
logWorker.reduceAndSendLogTask(logEntry);
}
private long extractEventTimestamp(LogEntry logEntry) {
if (logEntry.getTimeUnixNano() != null && logEntry.getTimeUnixNano() != 0) {
return logEntry.getTimeUnixNano() / 1_000_000; // Convert to milliseconds
@@ -115,7 +115,7 @@ public class WindowedLogRealTimeAlertCalculator implements Runnable {
}
return System.currentTimeMillis();
}
@PostConstruct
public void start() {
ThreadFactory threadFactory = new ThreadFactoryBuilder()
@@ -147,4 +147,4 @@ public class WindowedLogRealTimeAlertCalculator implements Runnable {
dispatcherExecutor.shutdownNow();
}
}
}
}
@@ -19,14 +19,14 @@ package org.apache.hertzbeat.alert.calculate.realtime.window;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import jakarta.annotation.PostConstruct;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import jakarta.annotation.PreDestroy;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
@@ -44,23 +44,23 @@ import java.util.concurrent.atomic.AtomicLong;
@Component
@Slf4j
public class TimeService {
static final long DEFAULT_WATERMARK_DELAY_MS = 30_000; // 30 seconds
private static final long WATERMARK_BROADCAST_INTERVAL_MS = 5_000; // 5 seconds
// Define acceptable timestamp range to filter abnormal timestamps
private static final long MAX_FUTURE_TIME_MS = 60_000; // Allow 1 minute future time
private static final long MAX_PAST_TIME_MS = 24 * 60 * 60 * 1000; // Allow 24 hours past time
private final AtomicLong maxTimestamp = new AtomicLong(0);
private final AtomicLong currentWatermark = new AtomicLong(0);
private final CopyOnWriteArrayList<WatermarkListener> listeners = new CopyOnWriteArrayList<>();
private ScheduledExecutorService scheduler;
public TimeService(List<WatermarkListener> initialListeners) {
listeners.addAll(initialListeners);
}
@PostConstruct
public void start() {
// Create internal scheduled executor
@@ -72,9 +72,9 @@ public class TimeService {
.setDaemon(true)
.setNameFormat("timeservice-scheduler-%d")
.build();
this.scheduler = Executors.newSingleThreadScheduledExecutor(threadFactory);
// Start watermark broadcast scheduler
scheduler.scheduleAtFixedRate(
this::broadcastWatermark,
@@ -82,10 +82,10 @@ public class TimeService {
WATERMARK_BROADCAST_INTERVAL_MS,
TimeUnit.MILLISECONDS
);
log.info("TimeService started with watermark delay: {}ms", DEFAULT_WATERMARK_DELAY_MS);
}
@PreDestroy
public void stop() {
if (scheduler != null && !scheduler.isShutdown()) {
@@ -105,10 +105,10 @@ public class TimeService {
Thread.currentThread().interrupt();
}
}
log.info("TimeService stopped");
}
/**
* Check if timestamp is within acceptable range
*/
@@ -116,35 +116,35 @@ public class TimeService {
long currentTime = System.currentTimeMillis();
return timestamp >= (currentTime - MAX_PAST_TIME_MS) && timestamp <= (currentTime + MAX_FUTURE_TIME_MS);
}
/**
* Check if data is late based on current watermark
*/
public boolean isLateData(long timestamp) {
return timestamp < getCurrentWatermark();
}
/**
* Update max timestamp from WindowedLogRealTimeAlertCalculator
*/
public void updateMaxTimestamp(long timestamp) {
maxTimestamp.getAndUpdate(current -> Math.max(current, timestamp));
}
/**
* Add watermark listener
*/
public void addWatermarkListener(WatermarkListener listener) {
listeners.add(listener);
}
/**
* Remove watermark listener
*/
public void removeWatermarkListener(WatermarkListener listener) {
listeners.remove(listener);
}
/**
* Get current watermark
*/
@@ -158,12 +158,12 @@ public class TimeService {
public long getMaxTimestamp() {
return maxTimestamp.get();
}
/**
* Calculate and broadcast watermark
*/
private void broadcastWatermark() {
try {
long maxTs = maxTimestamp.get();
if (maxTs <= 0) {
@@ -172,7 +172,7 @@ public class TimeService {
// Calculate watermark: maxTimestamp - delay
long newWatermark = maxTs - DEFAULT_WATERMARK_DELAY_MS;
long currentWm = currentWatermark.get();
// Only advance watermark (monotonic property)
if (newWatermark <= currentWm) {
return;
@@ -193,7 +193,7 @@ public class TimeService {
log.error("Error in watermark broadcast: {}", e.getMessage(), e);
}
}
/**
* Watermark data class
*/
@@ -203,11 +203,11 @@ public class TimeService {
public static class Watermark {
private final long timestamp;
}
/**
* Interface for watermark listeners
*/
public interface WatermarkListener {
void onWatermark(Watermark watermark);
}
}
}
@@ -18,6 +18,7 @@
package org.apache.hertzbeat.alert.calculate.realtime.window;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import jakarta.annotation.PostConstruct;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
@@ -25,8 +26,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import jakarta.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.Iterator;
@@ -51,14 +51,14 @@ import java.util.concurrent.TimeUnit;
@Component
@Slf4j
public class WindowAggregator implements TimeService.WatermarkListener, Runnable {
private static final long DEFAULT_WINDOW_SIZE_MS = 1 * 60 * 1000; // 1 minutes
private final AlarmEvaluator alarmEvaluator;
private final BlockingQueue<MatchingLogEvent> eventQueue = new LinkedBlockingQueue<>();
private final Map<WindowKey, WindowData> activeWindows = new HashMap<>();
private final Object windowLock = new Object();
private ExecutorService aggregatorExecutor;
public WindowAggregator(AlarmEvaluator alarmEvaluator) {
@@ -73,31 +73,31 @@ public class WindowAggregator implements TimeService.WatermarkListener, Runnable
log.warn("Interrupted while adding matching log to aggregator");
}
}
@Override
public void onWatermark(TimeService.Watermark watermark) {
List<WindowData> closedWindows;
synchronized (windowLock) {
closedWindows = new ArrayList<>();
// Find windows that should be closed based on watermark
Iterator<Map.Entry<WindowKey, WindowData>> iterator = activeWindows.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<WindowKey, WindowData> entry = iterator.next();
WindowData windowData = entry.getValue();
// Close window if its end time <= watermark timestamp
if (windowData.getEndTime() <= watermark.getTimestamp()) {
closedWindows.add(windowData);
iterator.remove();
log.debug("Closing window: {} with {} matching logs",
log.debug("Closing window: {} with {} matching logs",
entry.getKey(), windowData.getMatchingLogs().size());
}
}
}
}
for (WindowData windowData : closedWindows) {
alarmEvaluator.sendAndProcessWindowData(windowData);
}
@@ -117,17 +117,17 @@ public class WindowAggregator implements TimeService.WatermarkListener, Runnable
}
}
}
private void processMatchingLogEvent(MatchingLogEvent event) {
// Determine window size from alert define (if specified) or use default
long windowSizeMs = getWindowSize(event.getAlertDefine());
// Calculate window boundaries
long eventTime = event.getEventTimestamp();
long windowStart = (eventTime / windowSizeMs) * windowSizeMs;
long windowEnd = windowStart + windowSizeMs;
// Create window key
WindowKey windowKey = new WindowKey(
event.getAlertDefine().getId(),
@@ -136,15 +136,15 @@ public class WindowAggregator implements TimeService.WatermarkListener, Runnable
);
synchronized (windowLock) {
// Get or create window data
WindowData windowData = activeWindows.computeIfAbsent(windowKey,
WindowData windowData = activeWindows.computeIfAbsent(windowKey,
key -> new WindowData(key, event.getAlertDefine()));
// Add matching log to window
windowData.addMatchingLog(event);
log.debug("Added matching log to window: {} (total logs: {})",
log.debug("Added matching log to window: {} (total logs: {})",
windowKey, windowData.getMatchingLogs().size());
}
}
private long getWindowSize(AlertDefine alertDefine) {
// Check if alert define has custom window size configuration
if (alertDefine.getPeriod() != null) {
@@ -154,7 +154,7 @@ public class WindowAggregator implements TimeService.WatermarkListener, Runnable
DEFAULT_WINDOW_SIZE_MS, alertDefine.getName());
return DEFAULT_WINDOW_SIZE_MS;
}
@PostConstruct
public void start() {
// Create internal executor
@@ -166,15 +166,15 @@ public class WindowAggregator implements TimeService.WatermarkListener, Runnable
.setDaemon(true)
.setNameFormat("window-aggregator-%d")
.build();
aggregatorExecutor = Executors.newSingleThreadExecutor(threadFactory);
// Submit aggregation task
aggregatorExecutor.submit(this);
log.info("WindowAggregator started");
}
@PreDestroy
public void stop() {
if (aggregatorExecutor != null && !aggregatorExecutor.isShutdown()) {
@@ -194,10 +194,10 @@ public class WindowAggregator implements TimeService.WatermarkListener, Runnable
Thread.currentThread().interrupt();
}
}
log.info("WindowAggregator stopped");
}
/**
* Window key for identifying unique windows
*/
@@ -209,7 +209,7 @@ public class WindowAggregator implements TimeService.WatermarkListener, Runnable
private final long startTime;
private final long endTime;
}
/**
* Window data container
*/
@@ -221,13 +221,13 @@ public class WindowAggregator implements TimeService.WatermarkListener, Runnable
private final List<MatchingLogEvent> matchingLogs = new ArrayList<>();
@Getter
private final long createdTime;
public WindowData(WindowKey windowKey, AlertDefine alertDefine) {
this.windowKey = windowKey;
this.alertDefine = alertDefine;
this.createdTime = System.currentTimeMillis();
}
public void addMatchingLog(MatchingLogEvent event) {
matchingLogs.add(event);
}
@@ -248,4 +248,4 @@ public class WindowAggregator implements TimeService.WatermarkListener, Runnable
*/
public long getEndTime() { return windowKey.getEndTime(); }
}
}
}
@@ -17,12 +17,10 @@
package org.apache.hertzbeat.alert.notice.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.mail.internet.MimeMessage;
import java.util.Date;
import java.util.Properties;
import java.util.ResourceBundle;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.entity.dto.MailServerConfig;
import org.apache.hertzbeat.alert.notice.AlertNoticeException;
@@ -31,6 +29,7 @@ import org.apache.hertzbeat.common.entity.manager.GeneralConfig;
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.apache.hertzbeat.common.support.event.SystemConfigChangeEvent;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.apache.hertzbeat.common.util.ResourceBundleUtil;
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
import org.springframework.beans.factory.annotation.Value;
@@ -44,7 +43,6 @@ import org.springframework.stereotype.Component;
* Send alarm information through email
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class EmailAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl {
@@ -70,12 +68,15 @@ public class EmailAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl
private final GeneralConfigDao generalConfigDao;
private final ObjectMapper objectMapper;
private static final String TYPE = "email";
private ResourceBundle bundle = ResourceBundleUtil.getBundle("alerter");
public EmailAlertNotifyHandlerImpl(JavaMailSender javaMailSender, GeneralConfigDao generalConfigDao) {
this.javaMailSender = javaMailSender;
this.generalConfigDao = generalConfigDao;
}
@Override
public void send(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) throws AlertNoticeException {
try {
@@ -88,8 +89,8 @@ public class EmailAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerImpl
if (emailConfig != null && emailConfig.getContent() != null) {
// enable database configuration
String content = emailConfig.getContent();
MailServerConfig emailNoticeSenderConfig = objectMapper.readValue(content, MailServerConfig.class);
if (emailNoticeSenderConfig.isEnable()) {
MailServerConfig emailNoticeSenderConfig = JsonUtil.fromJson(content, MailServerConfig.class);
if (emailNoticeSenderConfig != null && emailNoticeSenderConfig.isEnable()) {
sender.setHost(emailNoticeSenderConfig.getEmailHost());
sender.setPort(emailNoticeSenderConfig.getEmailPort());
sender.setUsername(emailNoticeSenderConfig.getEmailUsername());
@@ -18,9 +18,6 @@
package org.apache.hertzbeat.alert.notice.impl;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@@ -49,6 +46,7 @@ import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
import tools.jackson.databind.JsonNode;
/**
* FeiShu app alert notify impl
@@ -78,7 +76,6 @@ public class FeiShuAppAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerI
*/
private static final String APP_BATCH_MESSAGE_URL = "https://open.feishu.cn/open-apis/message/v4/batch_send/";
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final byte USER_RECEIVE_TYPE = 0;
private static final byte CHAT_RECEIVE_TYPE = 1;
private static final byte PART_RECEIVE_TYPE = 2;
@@ -134,7 +131,7 @@ public class FeiShuAppAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerI
* @param messageContent Message content
* @see <a href="https://open.feishu.cn/document/server-docs/im-v1/batch_message/send-messages-in-batches">send message</a>
*/
private void sendLarkMessage(String accessToken, String receiverIdType, String receiverId, JsonNode messageContent) throws JsonProcessingException {
private void sendLarkMessage(String accessToken, String receiverIdType, String receiverId, JsonNode messageContent) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth(accessToken);
@@ -246,7 +243,7 @@ public class FeiShuAppAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerI
return response.getBody();
}
private JsonNode createLarkMessage(NoticeReceiver receiver, String notificationContent) throws JsonProcessingException {
private JsonNode createLarkMessage(NoticeReceiver receiver, String notificationContent) {
String larkCardMessage = """
{
"schema": "2.0",
@@ -359,12 +356,12 @@ public class FeiShuAppAlertNotifyHandlerImpl extends AbstractAlertNotifyHandlerI
String jsonStr = String.format(larkCardMessage,
notificationContent.replace("\"", "\\\"") + atUserElement,
alerterProperties.getConsoleUrl());
return OBJECT_MAPPER.readTree(jsonStr);
return JsonUtil.fromJson(jsonStr);
}
private String escapedCompactJson(JsonNode json) throws JsonProcessingException {
return OBJECT_MAPPER.writeValueAsString(json);
private String escapedCompactJson(JsonNode json) {
return json.toString();
}
/**
@@ -17,7 +17,6 @@
package org.apache.hertzbeat.alert.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.entity.dto.sms.SmsConfig;
import org.apache.hertzbeat.alert.service.impl.SmsLocalSmsClientImpl;
@@ -30,6 +29,7 @@ import org.apache.hertzbeat.base.dao.GeneralConfigDao;
import org.apache.hertzbeat.common.constants.GeneralConfigTypeEnum;
import org.apache.hertzbeat.common.entity.manager.GeneralConfig;
import org.apache.hertzbeat.common.support.event.SmsConfigChangeEvent;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@@ -50,14 +50,12 @@ public class SmsClientFactory {
private static final String TYPE = GeneralConfigTypeEnum.sms.name();
private final GeneralConfigDao generalConfigDao;
private final ObjectMapper objectMapper;
private final SmsConfig yamlSmsConfig;
private volatile SmsClient currentSmsClient;
public SmsClientFactory(GeneralConfigDao generalConfigDao, ObjectMapper objectMapper, SmsConfig yamlSmsConfig) {
public SmsClientFactory(GeneralConfigDao generalConfigDao, SmsConfig yamlSmsConfig) {
this.generalConfigDao = generalConfigDao;
this.objectMapper = objectMapper;
this.yamlSmsConfig = yamlSmsConfig;
}
@@ -118,7 +116,7 @@ public class SmsClientFactory {
try {
GeneralConfig config = generalConfigDao.findByType(TYPE);
if (config != null && config.getContent() != null) {
return objectMapper.readValue(config.getContent(), SmsConfig.class);
return JsonUtil.fromJson(config.getContent(), SmsConfig.class);
}
} catch (Exception e) {
log.error("[SmsClientFactory] Failed to load database configuration", e);
@@ -19,7 +19,6 @@ package org.apache.hertzbeat.alert.service.impl;
import static org.apache.hertzbeat.common.constants.ExportFileConstants.ExcelFile.FILE_SUFFIX;
import static org.apache.hertzbeat.common.constants.ExportFileConstants.ExcelFile.TYPE;
import com.fasterxml.jackson.core.type.TypeReference;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -42,6 +41,7 @@ import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import tools.jackson.core.type.TypeReference;
/**
* Configure the import and export EXCEL format
@@ -19,27 +19,22 @@ package org.apache.hertzbeat.alert.service.impl;
import static org.apache.hertzbeat.common.constants.ExportFileConstants.JsonFile.FILE_SUFFIX;
import static org.apache.hertzbeat.common.constants.ExportFileConstants.JsonFile.TYPE;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.dto.ExportAlertDefineDTO;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.springframework.stereotype.Service;
import tools.jackson.core.type.TypeReference;
/**
* Configure the import and export JSON format
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class AlertDefineJsonImExportServiceImpl extends AlertDefineAbstractImExportServiceImpl {
private final ObjectMapper objectMapper;
@Override
public String type() {
return TYPE;
@@ -52,22 +47,12 @@ public class AlertDefineJsonImExportServiceImpl extends AlertDefineAbstractImExp
@Override
public List<ExportAlertDefineDTO> parseImport(InputStream is) {
try {
return objectMapper.readValue(is, new TypeReference<>() {
});
} catch (IOException ex) {
log.error("import alertDefine failed.", ex);
throw new RuntimeException("import alertDefine failed");
}
return JsonUtil.fromJson(is, new TypeReference<>() {
});
}
@Override
public void writeOs(List<ExportAlertDefineDTO> exportAlertDefineList, OutputStream os) {
try {
objectMapper.writeValue(os, exportAlertDefineList);
} catch (IOException ex) {
log.error("export alertDefine failed.", ex);
throw new RuntimeException("export alertDefine failed");
}
JsonUtil.toJson(exportAlertDefineList, os);
}
}
@@ -17,9 +17,8 @@
package org.apache.hertzbeat.alert.service.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import tools.jackson.core.type.TypeReference;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.Predicate;
import jakarta.servlet.http.HttpServletResponse;
@@ -39,6 +38,7 @@ import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
import org.apache.hertzbeat.common.entity.manager.Label;
import org.apache.hertzbeat.common.util.FileUtil;
import org.apache.hertzbeat.common.util.JexlExpressionRunner;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
@@ -51,7 +51,6 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
@@ -98,7 +97,7 @@ public class AlertDefineServiceImpl implements AlertDefineService {
CommonConstants.LABEL_ALERT_SEVERITY,
CommonConstants.ALERT_MODE_LABEL
);
public AlertDefineServiceImpl(List<AlertDefineImExportService> alertDefineImExportServiceList, DataSourceService dataSourceService) {
alertDefineImExportServiceList.forEach(it -> alertDefineImExportServiceMap.put(it.type(), it));
this.dataSourceService = dataSourceService;
@@ -185,14 +184,9 @@ public class AlertDefineServiceImpl implements AlertDefineService {
@Override
public Page<AlertDefine> getAlertDefines(List<Long> defineIds, String search, String sort, String order, int pageIndex, int pageSize) {
// parse translation content list
ObjectMapper objectMapper = new ObjectMapper();
List<String> searchList = Collections.emptyList();
if (StringUtils.hasText(search)) {
try {
searchList = objectMapper.readValue(URLDecoder.decode(search, StandardCharsets.UTF_8), new TypeReference<>() {});
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Failed to parse search parameter", e);
}
searchList = JsonUtil.fromJson(URLDecoder.decode(search, StandardCharsets.UTF_8), new TypeReference<>() {});
}
List<String> finalSearchList = searchList;
// build search condition
@@ -17,7 +17,7 @@
package org.apache.hertzbeat.alert.service.impl;
import com.fasterxml.jackson.core.type.TypeReference;
import tools.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
@@ -199,4 +199,4 @@ public class AlibabaCloudSlsExternAlertService implements ExternAlertService {
}
}
}
}
@@ -17,7 +17,6 @@
package org.apache.hertzbeat.alert.service.impl;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.entity.dto.sms.AlibabaSmsProperties;
import org.apache.hertzbeat.alert.service.SmsClient;
@@ -45,6 +44,7 @@ import java.util.Map;
import java.util.SimpleTimeZone;
import java.util.TreeMap;
import java.util.UUID;
import tools.jackson.databind.JsonNode;
import static org.apache.hertzbeat.common.constants.SmsConstants.ALIBABA;
@@ -60,7 +60,7 @@ public class AlibabaSmsClientImpl implements SmsClient {
private static final String ACTION = "SendSms";
private static final String HOST = "dysmsapi.aliyuncs.com";
private static final String ALGORITHM = "ACS3-HMAC-SHA256";
private final String accessKeyId;
private final String accessKeySecret;
private final String signName;
@@ -151,7 +151,7 @@ public class AlibabaSmsClientImpl implements SmsClient {
httpPost.setHeader("x-acs-version", API_VERSION);
httpPost.setHeader("x-acs-date", timestamp);
httpPost.setHeader("x-acs-signature-nonce", nonce);
httpPost.setHeader("x-acs-content-sha256",
httpPost.setHeader("x-acs-content-sha256",
CryptoUtils.sha256Hex(""));
log.info("Sending Alibaba SMS request to {}", url + ", params: " + templateParam + "headers: " + Arrays.toString(httpPost.getAllHeaders()));
@@ -236,4 +236,4 @@ public class AlibabaSmsClientImpl implements SmsClient {
public boolean checkConfig() {
return !(accessKeyId.isBlank() || accessKeySecret.isBlank() || signName.isBlank() || templateCode.isBlank());
}
}
}
@@ -17,8 +17,7 @@
package org.apache.hertzbeat.alert.service.impl;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import tools.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.entity.dto.sms.AwsSmsProperties;
import org.apache.hertzbeat.alert.service.SmsClient;
@@ -110,7 +109,10 @@ public class AwsSmsClientImpl implements SmsClient {
String endpoint = "https://" + SERVICE + "." + region + ".amazonaws.com";
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
String payloadInString = new ObjectMapper().writeValueAsString(payload);
String payloadInString = JsonUtil.toJson(payload);
if (payloadInString == null) {
throw new SendMessageException("Failed to serialize SMS payload");
}
URI requestUri = new URI(endpoint);
HttpPost httpPost = createHttpPost(requestUri, amzDate, payloadInString);
@@ -17,7 +17,7 @@
package org.apache.hertzbeat.alert.service.impl;
import com.fasterxml.jackson.databind.JsonNode;
import tools.jackson.databind.JsonNode;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@@ -401,4 +401,4 @@ public class HuaweiCloudExternAlertService implements ExternAlertService {
this.endpoint = endpoint;
}
}
}
}
@@ -17,7 +17,7 @@
package org.apache.hertzbeat.alert.service.impl;
import com.fasterxml.jackson.core.type.TypeReference;
import tools.jackson.core.type.TypeReference;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
@@ -88,7 +88,7 @@ public class PrometheusExternAlertService implements ExternAlertService {
.triggerTimes(1)
.build();
alarmCommonReduce.reduceAndSendAlarm(singleAlert);
alarmCommonReduce.reduceAndSendAlarm(singleAlert);
}
}
@@ -17,7 +17,6 @@
package org.apache.hertzbeat.alert.service.impl;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.alert.dto.SkyWalkingExternAlert;
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
@@ -32,6 +31,7 @@ import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import tools.jackson.core.type.TypeReference;
/**
* SkyWalking external alarm service impl
@@ -62,7 +62,7 @@ public class SkyWalkingExternAlertService implements ExternAlertService {
.annotations(acquireAlertAnnotations(alert))
.triggerTimes(1)
.build();
alarmCommonReduce.reduceAndSendAlarm(singleAlert);
alarmCommonReduce.reduceAndSendAlarm(singleAlert);
}
}
@@ -17,7 +17,7 @@
package org.apache.hertzbeat.alert.service.impl;
import com.fasterxml.jackson.databind.JsonNode;
import tools.jackson.databind.JsonNode;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
@@ -33,7 +33,7 @@ import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.hertzbeat.common.util.JsonUtil;
import com.fasterxml.jackson.databind.JsonNode;
import tools.jackson.databind.JsonNode;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
@@ -59,7 +59,7 @@ public class TencentSmsClientImpl implements SmsClient {
private static final String ACTION = "SendSms";
private static final String HOST = "sms.tencentcloudapi.com";
private static final Charset UTF8 = StandardCharsets.UTF_8;
private String appId;
private String signName;
private String templateId;
@@ -107,7 +107,7 @@ public class TencentSmsClientImpl implements SmsClient {
String[] templateValues, String[] phones) {
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
// build request payload
Map<String, Object> params = new HashMap<>();
params.put("SmsSdkAppId", appId);
@@ -115,14 +115,14 @@ public class TencentSmsClientImpl implements SmsClient {
params.put("TemplateId", templateId);
params.put("TemplateParamSet", templateValues);
params.put("PhoneNumberSet", phones);
String payload = JsonUtil.toJson(params);
// calculate request signature
String authorization = calculateAuthorization(
secretId, secretKey, "sms", HOST, REGION,
ACTION, API_VERSION, payload);
// build http request
HttpPost httpPost = new HttpPost("https://" + HOST);
httpPost.setHeader("Content-Type", "application/json; charset=utf-8");
@@ -142,11 +142,11 @@ public class TencentSmsClientImpl implements SmsClient {
String responseBody = EntityUtils.toString(response.getEntity());
log.debug("SMS response status: {}, body: {}", statusCode, responseBody);
if (statusCode != 200) {
throw new SendMessageException("HTTP request failed with status code: " + statusCode);
}
JsonNode jsonResponse = JsonUtil.fromJson(responseBody);
JsonNode responseNode = jsonResponse.get("Response");
JsonNode error = responseNode.get("Error");
@@ -17,7 +17,7 @@
package org.apache.hertzbeat.alert.service.impl;
import com.fasterxml.jackson.databind.JsonNode;
import tools.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.entity.dto.sms.TwilioSmsProperties;
import org.apache.hertzbeat.alert.service.SmsClient;
@@ -31,7 +31,6 @@ import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import com.fasterxml.jackson.databind.JsonNode;
import org.apache.hertzbeat.alert.util.CryptoUtils;
import java.io.IOException;
@@ -41,6 +40,7 @@ import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
import java.util.stream.Collectors;
import tools.jackson.databind.JsonNode;
import static org.apache.hertzbeat.common.constants.SmsConstants.UNISMS;
@@ -55,7 +55,7 @@ public class UniSmsClientImpl implements SmsClient {
private static final String ACTION = "sms.message.send";
private static final String SUCCESS_CODE = "0";
private static final String HMAC_ALGORITHM = "hmac-sha256";
private final UniSmsProperties config;
public UniSmsClientImpl(UniSmsProperties config) {
@@ -80,7 +80,7 @@ public class UniSmsClientImpl implements SmsClient {
if (content == null) {
content = alert.getCommonAnnotations().values().stream().findFirst().orElse(null);
}
templateData.put("instance", instance);
templateData.put("priority", priority);
templateData.put("content", content);
@@ -98,7 +98,7 @@ public class UniSmsClientImpl implements SmsClient {
HttpPost httpPost = new HttpPost(url);
httpPost.setHeader("Content-Type", "application/json");
httpPost.setHeader("Accept", "application/json");
String payload = JsonUtil.toJson(params);
httpPost.setEntity(new StringEntity(payload, StandardCharsets.UTF_8));
@@ -114,14 +114,14 @@ public class UniSmsClientImpl implements SmsClient {
}
private String buildSimpleUrl() {
return String.format("%s/?action=%s&accessKeyId=%s",
return String.format("%s/?action=%s&accessKeyId=%s",
API_URL, ACTION, config.getAccessKeyId());
}
private String buildHmacUrl() {
long timestamp = System.currentTimeMillis();
String nonce = generateNonce();
// build query parameters
Map<String, String> params = new TreeMap<>();
params.put("accessKeyId", config.getAccessKeyId());
@@ -134,7 +134,7 @@ public class UniSmsClientImpl implements SmsClient {
String signText = params.entrySet().stream()
.map(entry -> entry.getKey() + "=" + entry.getValue())
.collect(Collectors.joining("&"));
// calculate signature
String signature = CryptoUtils.hmacSha256Base64(config.getAccessKeySecret(), signText);
return String.format("%s/?action=%s&accessKeyId=%s&algorithm=%s&timestamp=%d&nonce=%s&signature=%s",
@@ -148,20 +148,20 @@ public class UniSmsClientImpl implements SmsClient {
private void handleResponse(CloseableHttpResponse response, String phone) throws IOException {
int statusCode = response.getStatusLine().getStatusCode();
String responseBody = EntityUtils.toString(response.getEntity());
log.info("UniSMS response status: {}, body: {}", statusCode, responseBody);
if (statusCode != 200) {
throw new SendMessageException("HTTP request failed with status code: " + statusCode + ", response: " + responseBody);
}
JsonNode jsonResponse = JsonUtil.fromJson(responseBody);
String code = jsonResponse.get("code").asText();
if (!SUCCESS_CODE.equals(code)) {
String message = jsonResponse.get("message").asText();
throw new SendMessageException(code + ":" + message);
}
log.info("Successfully sent SMS to phone: {}", phone);
}
@@ -172,22 +172,22 @@ public class UniSmsClientImpl implements SmsClient {
@Override
public boolean checkConfig() {
if (config == null
|| config.getAccessKeyId() == null
if (config == null
|| config.getAccessKeyId() == null
|| config.getAccessKeyId().isBlank()
|| config.getSignature() == null
|| config.getSignature() == null
|| config.getSignature().isBlank()
|| config.getTemplateId() == null
|| config.getTemplateId() == null
|| config.getTemplateId().isBlank()) {
return false;
}
// HMAC mode requires additional check for accessKeySecret
if ("hmac".equalsIgnoreCase(config.getAuthMode())
if ("hmac".equalsIgnoreCase(config.getAuthMode())
&& (config.getAccessKeySecret() == null || config.getAccessKeySecret().isBlank())) {
return false;
}
return true;
}
}
}
@@ -17,10 +17,8 @@
package org.apache.hertzbeat.alert.service.impl;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import tools.jackson.databind.JsonNode;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.hertzbeat.alert.dto.VolcEngineExternEventAlert;
@@ -56,10 +54,12 @@ public class VolcEngineExternAlertService implements ExternAlertService {
);
@Override
@SneakyThrows
public void addExternAlert(String content) {
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(content);
JsonNode root = JsonUtil.fromJson(content);
if (root == null) {
log.warn("parse extern alert content failed! content: {}", content);
return;
}
String type = root.get("Type").asText();
if (VolcEngineExternMetricAlert.ALERT_TYPE_EVENT.equals(type)) {
VolcEngineExternEventAlert eventAlert = JsonUtil.fromJson(content, VolcEngineExternEventAlert.class);
@@ -19,13 +19,10 @@ package org.apache.hertzbeat.alert.notice.impl;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Properties;
import org.apache.hertzbeat.common.entity.dto.MailServerConfig;
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
@@ -35,6 +32,7 @@ import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate;
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
import org.apache.hertzbeat.alert.notice.AlertNoticeException;
import org.apache.hertzbeat.common.entity.manager.GeneralConfig;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -57,18 +55,15 @@ class EmailAlertNotifyHandlerImplTest {
@Mock
private JavaMailSenderImpl mailSender;
@Mock
private ResourceBundle bundle;
@Mock
private GeneralConfigDao generalConfigDao;
@Mock
private MimeMessage mimeMessage;
@Mock
private ObjectMapper objectMapper;
private ResourceBundle bundle;
@Mock
private GeneralConfigDao generalConfigDao;
@Mock
private MimeMessage mimeMessage;
@InjectMocks
private EmailAlertNotifyHandlerImpl emailAlertNotifyHandler;
@@ -78,27 +73,27 @@ class EmailAlertNotifyHandlerImplTest {
private NoticeTemplate template;
@BeforeEach
public void setUp() throws JsonProcessingException {
public void setUp() {
receiver = new NoticeReceiver();
receiver.setId(1L);
receiver.setName("test-receiver");
receiver.setEmail("test@example.com");
groupAlert = new GroupAlert();
SingleAlert singleAlert = new SingleAlert();
singleAlert.setLabels(new HashMap<>());
singleAlert.getLabels().put("severity", "critical");
singleAlert.getLabels().put("alertname", "Test Alert");
List<SingleAlert> alerts = new ArrayList<>();
alerts.add(singleAlert);
groupAlert.setAlerts(alerts);
template = new NoticeTemplate();
template.setId(1L);
template.setName("test-template");
template.setContent("test content");
// Set up email server configuration
MailServerConfig mailServerConfig = new MailServerConfig();
mailServerConfig.setEmailHost("smtp.example.com");
@@ -106,17 +101,18 @@ class EmailAlertNotifyHandlerImplTest {
mailServerConfig.setEmailUsername("sender@example.com");
mailServerConfig.setEmailPassword("password");
mailServerConfig.setEnable(true);
GeneralConfig generalConfig = GeneralConfig.builder().content("").build();
GeneralConfig generalConfig = GeneralConfig.builder()
.content(JsonUtil.toJson(mailServerConfig))
.build();
when(generalConfigDao.findByType(any())).thenReturn(generalConfig);
when(objectMapper.readValue(any(String.class), eq(MailServerConfig.class)))
.thenReturn(mailServerConfig);
when(mailSender.getJavaMailProperties()).thenReturn(new Properties());
}
@Test
public void testNotifyAlertWithInvalidEmail() {
receiver.setEmail(null);
assertThrows(AlertNoticeException.class,
assertThrows(AlertNoticeException.class,
() -> emailAlertNotifyHandler.send(receiver, template, groupAlert));
}
@@ -131,7 +127,7 @@ class EmailAlertNotifyHandlerImplTest {
@Test
public void testNotifyAlertFailure() {
when(mailSender.createMimeMessage()).thenThrow(new RuntimeException("Test Error"));
assertThrows(AlertNoticeException.class,
assertThrows(AlertNoticeException.class,
() -> emailAlertNotifyHandler.send(receiver, template, groupAlert));
}
}
@@ -19,20 +19,11 @@ package org.apache.hertzbeat.alert.service;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import org.apache.hertzbeat.alert.dto.AlertDefineDTO;
@@ -40,22 +31,13 @@ import org.apache.hertzbeat.alert.dto.ExportAlertDefineDTO;
import org.apache.hertzbeat.alert.service.impl.AlertDefineJsonImExportServiceImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
/**
* test case for {@link AlertDefineJsonImExportServiceImpl}
*/
@ExtendWith(MockitoExtension.class)
class AlertDefineJsonImExportServiceTest {
@Mock
private ObjectMapper objectMapper;
@InjectMocks
private AlertDefineJsonImExportServiceImpl service;
@SuppressWarnings("checkstyle:OperatorWrap")
@@ -68,6 +50,7 @@ class AlertDefineJsonImExportServiceTest {
@BeforeEach
public void setup() {
service = new AlertDefineJsonImExportServiceImpl();
inputStream = new ByteArrayInputStream(JSON_DATA.getBytes(StandardCharsets.UTF_8));
@@ -87,59 +70,43 @@ class AlertDefineJsonImExportServiceTest {
}
@Test
void testParseImport() throws IOException {
when(objectMapper.readValue(
any(InputStream.class),
any(TypeReference.class))
).thenReturn(alertDefineList);
void testParseImport() {
List<ExportAlertDefineDTO> result = service.parseImport(inputStream);
assertNotNull(result);
assertEquals(1, result.size());
assertEquals(alertDefineList, result);
verify(objectMapper, times(1)).readValue(any(InputStream.class), any(TypeReference.class));
assertEquals("App1", result.get(0).getAlertDefine().getName());
assertEquals("realtime", result.get(0).getAlertDefine().getType());
}
@Test
void testParseImportFailed() throws IOException {
void testParseImportFailed() {
InputStream invalidInputStream = new ByteArrayInputStream("invalid json".getBytes(StandardCharsets.UTF_8));
when(objectMapper.readValue(
any(InputStream.class),
any(TypeReference.class))
).thenThrow(new IOException("Test Exception"));
List<ExportAlertDefineDTO> result = service.parseImport(invalidInputStream);
RuntimeException exception = assertThrows(RuntimeException.class, () -> service.parseImport(inputStream));
assertEquals("import alertDefine failed", exception.getMessage());
verify(objectMapper, times(1)).readValue(any(InputStream.class), any(TypeReference.class));
assertNull(result);
}
@Test
void testWriteOs() throws IOException {
void testWriteOs() {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
service.writeOs(alertDefineList, outputStream);
verify(objectMapper, times(1)).writeValue(any(OutputStream.class), eq(alertDefineList));
String result = outputStream.toString(StandardCharsets.UTF_8);
assertNotNull(result);
assertTrue(result.contains("App1"));
assertTrue(result.contains("realtime"));
}
@Test
void testWriteOsFailed() throws IOException {
doThrow(new IOException("Test Exception")).when(objectMapper).writeValue(any(OutputStream.class), any());
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
RuntimeException exception = assertThrows(
RuntimeException.class,
() -> service.writeOs(alertDefineList, outputStream)
);
assertEquals("export alertDefine failed", exception.getMessage());
verify(objectMapper, times(1)).writeValue(any(OutputStream.class), eq(alertDefineList));
void testType() {
assertEquals("JSON", service.type());
}
@Test
void testGetFileName() {
assertTrue(service.getFileName().endsWith(".json"));
}
}
@@ -17,8 +17,6 @@
package org.apache.hertzbeat.alert.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.hertzbeat.common.entity.dto.sms.SmsConfig;
import org.apache.hertzbeat.common.entity.dto.sms.SmslocalSmsProperties;
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
@@ -46,9 +44,6 @@ public class SmsClientFactoryTest {
@Mock
private GeneralConfigDao generalConfigDao;
@Mock
private ObjectMapper objectMapper;
@Mock
private SmsConfig yamlSmsConfig;
@@ -57,7 +52,7 @@ public class SmsClientFactoryTest {
@Test
void testloadDbConfig() throws JsonProcessingException {
void testloadDbConfig() {
GeneralConfig generalConfig = new GeneralConfig();
SmsConfig smsConfig = new SmsConfig();
@@ -66,7 +61,6 @@ public class SmsClientFactoryTest {
smsConfig.setSmslocal(new SmslocalSmsProperties("11"));
generalConfig.setContent(JsonUtil.toJson(smsConfig));
when(objectMapper.readValue(generalConfig.getContent(), SmsConfig.class)).thenReturn(smsConfig);
when(generalConfigDao.findByType(GeneralConfigTypeEnum.sms.name())).thenReturn(generalConfig);
assertNotNull(smsClientFactory.getSmsClient());
@@ -17,7 +17,6 @@
package org.apache.hertzbeat.collector.collect.push;
import com.fasterxml.jackson.core.type.TypeReference;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@@ -48,6 +47,7 @@ import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.springframework.http.MediaType;
import tools.jackson.core.type.TypeReference;
/**
* push style collect
@@ -17,7 +17,7 @@
package org.apache.hertzbeat.collector.collect.sd;
import com.fasterxml.jackson.core.type.TypeReference;
import tools.jackson.core.type.TypeReference;
import com.google.common.collect.Lists;
import java.io.IOException;
import java.net.MalformedURLException;
@@ -21,9 +21,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import java.util.ArrayList;
@@ -36,6 +33,7 @@ import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import org.apache.hertzbeat.common.entity.job.Configmap;
import org.apache.hertzbeat.common.entity.job.Metrics;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
@@ -46,8 +44,6 @@ import org.junit.jupiter.params.provider.MethodSource;
*/
class CollectUtilTest {
private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
@Test
void countMatchKeyword() {
int keyword = CollectUtil.countMatchKeyword("hertzbeat", "e");
@@ -115,7 +111,7 @@ class CollectUtilTest {
}
@Test
void replaceCryPlaceholder() throws JsonMappingException, JsonProcessingException {
void replaceCryPlaceholder() {
Metrics metrics = Metrics.builder().name("^o^name^o^").build();
JsonElement jsonElement = new Gson().toJsonTree(metrics);
Map<String, Configmap> configmap = new HashMap<>();
@@ -125,7 +121,7 @@ class CollectUtilTest {
Metrics metricsTarget = Metrics.builder().name("张三").build();
JsonElement jsonElementTarget = new Gson().toJsonTree(metricsTarget);
assertEquals(JSON_MAPPER.readTree(jsonElementTarget.toString()), JSON_MAPPER.readTree(res1.toString()));
assertEquals(JsonUtil.fromJson(jsonElementTarget.toString()), JsonUtil.fromJson(res1.toString()));
List<Metrics> metricsList = new ArrayList<>();
metricsList.add(metrics);
@@ -137,7 +133,7 @@ class CollectUtilTest {
metricsListTarget.add(metricsTarget);
metricsListTarget.add(metricsTarget);
JsonElement jsonArrayTarget = new Gson().toJsonTree(metricsListTarget);
assertEquals(JSON_MAPPER.readTree(jsonArrayTarget.toString()), JSON_MAPPER.readTree(res2.toString()));
assertEquals(JsonUtil.fromJson(jsonArrayTarget.toString()), JsonUtil.fromJson(res2.toString()));
}
static Stream<Arguments> testParamsForShouldVerifyReplaceCryPlaceholder() {
@@ -181,10 +177,10 @@ class CollectUtilTest {
@MethodSource("testParamsForShouldVerifyReplaceCryPlaceholder")
void shouldVerifyReplaceCryPlaceholder(JsonObject jsonObject,
Map<String, Configmap> configmap,
JsonObject jsonObjectTarget) throws JsonProcessingException {
JsonObject jsonObjectTarget) {
JsonElement res1 = CollectUtil.replaceCryPlaceholder(jsonObject, configmap);
assertEquals(JSON_MAPPER.readTree(jsonObjectTarget.toString()), JSON_MAPPER.readTree(res1.toString()));
assertEquals(JsonUtil.fromJson(jsonObjectTarget.toString()), JsonUtil.fromJson(res1.toString()));
List<JsonObject> metricsList = new ArrayList<>();
metricsList.add(jsonObject);
@@ -195,11 +191,11 @@ class CollectUtilTest {
metricsListTarget.add(jsonObjectTarget);
JsonElement jsonArrayTarget = new Gson().toJsonTree(metricsListTarget);
assertEquals(JSON_MAPPER.readTree(jsonArrayTarget.toString()), JSON_MAPPER.readTree(res2.toString()));
assertEquals(JsonUtil.fromJson(jsonArrayTarget.toString()), JsonUtil.fromJson(res2.toString()));
}
@Test
void replaceSmilingPlaceholder() throws JsonMappingException, JsonProcessingException {
void replaceSmilingPlaceholder() {
Metrics metrics = Metrics.builder().name("^_^name^_^").build();
JsonElement jsonElement = new Gson().toJsonTree(metrics);
HashMap<String, Configmap> configmap = new HashMap<>();
@@ -208,7 +204,7 @@ class CollectUtilTest {
JsonElement res = CollectUtil.replaceSmilingPlaceholder(jsonElement, configmap);
Metrics metricsTarget = Metrics.builder().name("张三").build();
JsonElement jsonElement2 = new Gson().toJsonTree(metricsTarget);
assertEquals(JSON_MAPPER.readTree(jsonElement2.toString()), JSON_MAPPER.readTree(res.toString()));
assertEquals(JsonUtil.fromJson(jsonElement2.toString()), JsonUtil.fromJson(res.toString()));
List<Metrics> metricsList = new ArrayList<>();
metricsList.add(metrics);
@@ -220,7 +216,7 @@ class CollectUtilTest {
metricsListTarget.add(metricsTarget);
metricsListTarget.add(metricsTarget);
JsonElement jsonArrayTarget = new Gson().toJsonTree(metricsListTarget);
assertEquals(JSON_MAPPER.readTree(jsonArrayTarget.toString()), JSON_MAPPER.readTree(res2.toString()));
assertEquals(JsonUtil.fromJson(jsonArrayTarget.toString()), JsonUtil.fromJson(res2.toString()));
}
@Test
@@ -57,10 +57,6 @@
<artifactId>sshd-core</artifactId>
<version>${sshd-core.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
@@ -85,4 +81,4 @@
<artifactId>gson</artifactId>
</dependency>
</dependencies>
</project>
</project>
@@ -17,7 +17,7 @@
package org.apache.hertzbeat.collector.dispatch.entrance.processor;
import com.fasterxml.jackson.core.type.TypeReference;
import tools.jackson.core.type.TypeReference;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.collector.dispatch.entrance.CollectServer;
@@ -47,7 +47,7 @@ public class DeleteCyclicTaskProcessor implements NettyRemotingProcessor {
return null;
}
for (Long jobId : jobIds) {
collectServer.getCollectJobService().cancelAsyncCollectJob(jobId);
collectServer.getCollectJobService().cancelAsyncCollectJob(jobId);
}
return null;
}
@@ -18,7 +18,7 @@
package org.apache.hertzbeat.collector.util;
import com.beetstra.jutf7.CharsetProvider;
import com.fasterxml.jackson.core.type.TypeReference;
import tools.jackson.core.type.TypeReference;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
+1 -1
View File
@@ -28,7 +28,7 @@
<name>${project.artifactId}</name>
<packaging>pom</packaging>
<properties>
<java.version>17</java.version>
<java.version>21</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
</properties>
+15 -5
View File
@@ -131,26 +131,36 @@
<version>${javaparser.version}</version>
<scope>test</scope>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
</dependency>
<!-- jackson for optional json processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<groupId>tools.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>tools.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
<!-- swagger annotations (optional) -->
<dependency>
<groupId>org.springdoc</groupId>
@@ -6,7 +6,7 @@
* (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
* 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,
@@ -17,14 +17,8 @@
package org.apache.hertzbeat.common.entity.dto;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.google.gson.JsonObject;
import java.io.IOException;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
@@ -32,6 +26,11 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;
import tools.jackson.core.JsonParser;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ValueDeserializer;
import tools.jackson.databind.annotation.JsonDeserialize;
/**
* prometheus vector or matrix entity
@@ -42,6 +41,7 @@ import lombok.experimental.Accessors;
@Accessors(chain = true)
@ToString
@Getter
@JsonIgnoreProperties(ignoreUnknown = true)
public class PromVectorOrMatrix {
private String status;
private Data data;
@@ -81,15 +81,18 @@ public class PromVectorOrMatrix {
@lombok.Data
@NoArgsConstructor
@Accessors(chain = true)
public static class MetricJsonObjectDeserializer extends JsonDeserializer<JsonObject>{
public static class MetricJsonObjectDeserializer extends ValueDeserializer<JsonObject> {
@Override
public JsonObject deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
ObjectCodec oc = jp.getCodec();
JsonNode node = oc.readTree(jp);
public JsonObject deserialize(JsonParser jp, DeserializationContext ctxt) {
JsonNode node = jp.objectReadContext().readTree(jp);
JsonObject metric = new JsonObject();
node.fields().forEachRemaining(entry -> metric.addProperty(entry.getKey(), entry.getValue().asText()));
if (node != null && node.isObject()) {
node.properties().forEach(entry ->
metric.addProperty(entry.getKey(), entry.getValue().asString())
);
}
return metric;
}
@@ -17,15 +17,16 @@
package org.apache.hertzbeat.common.entity.sd;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import tools.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import tools.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import tools.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
/**
* eureka service discovery entity
@@ -17,12 +17,11 @@
package org.apache.hertzbeat.common.serialize;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.entity.log.LogEntry;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.apache.kafka.common.header.Headers;
import org.apache.kafka.common.serialization.Deserializer;
@@ -32,12 +31,6 @@ import org.apache.kafka.common.serialization.Deserializer;
@Slf4j
public class KafkaLogEntryDeserializer implements Deserializer<LogEntry> {
private final ObjectMapper objectMapper;
public KafkaLogEntryDeserializer() {
this.objectMapper = new ObjectMapper();
}
@Override
public void configure(Map<String, ?> configs, boolean isKey) {
Deserializer.super.configure(configs, isKey);
@@ -49,13 +42,12 @@ public class KafkaLogEntryDeserializer implements Deserializer<LogEntry> {
log.warn("Empty data received for topic: {}", topic);
return null;
}
try {
String jsonString = new String(data, StandardCharsets.UTF_8);
return objectMapper.readValue(jsonString, LogEntry.class);
} catch (JsonProcessingException e) {
log.error("Failed to deserialize LogEntry from JSON for topic: {}", topic, e);
return null;
String jsonString = new String(data, StandardCharsets.UTF_8);
LogEntry logEntry = JsonUtil.fromJson(jsonString, LogEntry.class);
if (logEntry == null) {
log.warn("Failed to deserialize LogEntry from topic: {}", topic);
}
return logEntry;
}
@Override
@@ -67,4 +59,4 @@ public class KafkaLogEntryDeserializer implements Deserializer<LogEntry> {
public void close() {
Deserializer.super.close();
}
}
}
@@ -17,12 +17,11 @@
package org.apache.hertzbeat.common.serialize;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.entity.log.LogEntry;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.apache.kafka.common.header.Headers;
import org.apache.kafka.common.serialization.Serializer;
@@ -32,12 +31,6 @@ import org.apache.kafka.common.serialization.Serializer;
@Slf4j
public class KafkaLogEntrySerializer implements Serializer<LogEntry> {
private final ObjectMapper objectMapper;
public KafkaLogEntrySerializer() {
this.objectMapper = new ObjectMapper();
}
@Override
public void configure(Map<String, ?> configs, boolean isKey) {
Serializer.super.configure(configs, isKey);
@@ -49,13 +42,12 @@ public class KafkaLogEntrySerializer implements Serializer<LogEntry> {
log.warn("LogEntry is null for topic: {}", topic);
return null;
}
try {
String jsonString = objectMapper.writeValueAsString(logEntry);
return jsonString.getBytes(StandardCharsets.UTF_8);
} catch (JsonProcessingException e) {
log.error("Failed to serialize LogEntry to JSON for topic: {}", topic, e);
String jsonString = JsonUtil.toJson(logEntry);
if (jsonString == null) {
log.error("Failed to serialize LogEntry for topic: {}", topic);
return null;
}
return jsonString.getBytes(StandardCharsets.UTF_8);
}
@Override
@@ -67,4 +59,4 @@ public class KafkaLogEntrySerializer implements Serializer<LogEntry> {
public void close() {
Serializer.super.close();
}
}
}
@@ -17,11 +17,11 @@
package org.apache.hertzbeat.common.serialize;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.lettuce.core.codec.RedisCodec;
import io.netty.buffer.Unpooled;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.entity.log.LogEntry;
import org.apache.hertzbeat.common.util.JsonUtil;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
@@ -32,12 +32,6 @@ import java.nio.charset.StandardCharsets;
@Slf4j
public class RedisLogEntryCodec implements RedisCodec<String, LogEntry> {
private final ObjectMapper objectMapper;
public RedisLogEntryCodec() {
this.objectMapper = new ObjectMapper();
}
@Override
public String decodeKey(ByteBuffer byteBuffer) {
return Unpooled.wrappedBuffer(byteBuffer).toString(StandardCharsets.UTF_8);
@@ -48,13 +42,12 @@ public class RedisLogEntryCodec implements RedisCodec<String, LogEntry> {
if (byteBuffer == null || !byteBuffer.hasRemaining()) {
return null;
}
try {
String jsonString = Unpooled.wrappedBuffer(byteBuffer).toString(StandardCharsets.UTF_8);
return objectMapper.readValue(jsonString, LogEntry.class);
} catch (Exception e) {
log.error("Failed to decode LogEntry from JSON: {}", e.getMessage());
return null;
String jsonString = Unpooled.wrappedBuffer(byteBuffer).toString(StandardCharsets.UTF_8);
LogEntry logEntry = JsonUtil.fromJson(jsonString, LogEntry.class);
if (logEntry == null) {
log.error("Failed to decode LogEntry from JSON");
}
return logEntry;
}
@Override
@@ -64,12 +57,11 @@ public class RedisLogEntryCodec implements RedisCodec<String, LogEntry> {
@Override
public ByteBuffer encodeValue(LogEntry logEntry) {
try {
String jsonString = objectMapper.writeValueAsString(logEntry);
return ByteBuffer.wrap(jsonString.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
log.error("Failed to encode LogEntry to JSON: {}", e.getMessage());
String jsonString = JsonUtil.toJson(logEntry);
if (jsonString == null) {
log.error("Failed to encode LogEntry to JSON");
return null;
}
return ByteBuffer.wrap(jsonString.getBytes(StandardCharsets.UTF_8));
}
}
}
@@ -19,7 +19,6 @@
package org.apache.hertzbeat.common.util;
import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.collect.Maps;
import org.apache.commons.jexl3.JexlBuilder;
import org.apache.commons.jexl3.JexlContext;
@@ -32,6 +31,7 @@ import org.apache.hertzbeat.common.constants.CommonConstants;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.function.Function;
import tools.jackson.core.type.TypeReference;
/**
* jexl express runner
@@ -40,7 +40,7 @@ public class JexlExpressionRunner {
private static final String LOADER_NAME = "jexl-class-loader";
private static final JexlEngine jexlEngine;
static {
Map<String, Object> functions = Maps.newLinkedHashMap();
// set the root namespace function
@@ -59,7 +59,7 @@ public class JexlExpressionRunner {
.features(features).strict(true).silent(false).stackOverflow(40).namespaces(functions)
.create();
}
public static Object evaluate(String expression, Map<String, Object> context) {
JexlContext jexlContext = new MapContext();
for (Map.Entry<String, Object> entry : context.entrySet()) {
@@ -75,11 +75,11 @@ public class JexlExpressionRunner {
}
return expression.evaluate(jexlContext);
}
public static Object evaluate(String expression) {
return jexlEngine.createExpression(expression).evaluate(new MapContext());
}
public static JexlExpression compile(String expression) {
return jexlEngine.createExpression(expression);
}
@@ -19,15 +19,18 @@ package org.apache.hertzbeat.common.util;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import javax.annotation.concurrent.ThreadSafe;
import java.io.InputStream;
import java.io.OutputStream;
import lombok.extern.slf4j.Slf4j;
import tools.jackson.core.JacksonException;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.SerializationFeature;
import tools.jackson.databind.json.JsonMapper;
import javax.annotation.concurrent.ThreadSafe;
/**
* json util
@@ -36,27 +39,25 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public final class JsonUtil {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final ObjectMapper OBJECT_MAPPER = JsonMapper.builder()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false)
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
.changeDefaultVisibility(vc -> vc.withVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY))
.build();
private JsonUtil() {
}
static {
OBJECT_MAPPER
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
.registerModule(new JavaTimeModule());
}
public static String toJson(Object source) {
if (source == null) {
return null;
}
try {
return OBJECT_MAPPER.writeValueAsString(source);
} catch (JsonProcessingException e) {
log.error(e.getMessage(), e);
} catch (JacksonException e) {
log.error("Error converting object to JSON: {}", e.getMessage(), e);
return null;
}
}
@@ -67,8 +68,8 @@ public final class JsonUtil {
}
try {
return OBJECT_MAPPER.readValue(jsonStr, clazz);
} catch (Exception e) {
log.error(e.getMessage(), e);
} catch (JacksonException e) {
log.error("Error parsing JSON to class {}: {}", clazz.getName(), e.getMessage(), e);
return null;
}
}
@@ -79,20 +80,20 @@ public final class JsonUtil {
}
try {
return OBJECT_MAPPER.readValue(jsonStr, type);
} catch (Exception e) {
log.error(e.getMessage(), e);
} catch (JacksonException e) {
log.error("Error parsing JSON to TypeReference: {}", e.getMessage(), e);
return null;
}
}
public static JsonNode fromJson(String jsonStr) {
if (jsonStr == null || jsonStr.trim().isEmpty()) {
return null;
}
try {
return OBJECT_MAPPER.readTree(jsonStr);
} catch (Exception e) {
log.error(e.getMessage(), e);
} catch (JacksonException e) {
log.error("Error reading JSON tree: {}", e.getMessage(), e);
return null;
}
}
@@ -102,21 +103,18 @@ public final class JsonUtil {
* @param jsonStr json string
* @return true if the string is a json string
*/
public static boolean isJsonStr(String jsonStr) {
if (jsonStr == null || jsonStr.trim().isEmpty()) {
return false;
}
jsonStr = jsonStr.trim();
if (!(jsonStr.startsWith("{") && jsonStr.endsWith("}"))
&& !(jsonStr.startsWith("[") && jsonStr.endsWith("]"))) {
if (!isJsonLike(jsonStr)) {
return false;
}
try {
OBJECT_MAPPER.readTree(jsonStr);
return true;
} catch (Exception ignored) {
} catch (JacksonException ignored) {
return false;
}
}
@@ -128,7 +126,7 @@ public final class JsonUtil {
try {
JsonNode jsonNode = OBJECT_MAPPER.readTree(jsonStr);
return jsonNode.isArray();
} catch (Exception ignore) {
} catch (JacksonException ignore) {
return false;
}
}
@@ -142,4 +140,56 @@ public final class JsonUtil {
char end = jsonStr.charAt(jsonStr.length() - 1);
return (start == '{' && end == '}') || (start == '[' && end == ']');
}
/**
* Parse JSON from InputStream to object
* @param is input stream
* @param type type reference
* @return parsed object or null if error
*/
public static <T> T fromJson(InputStream is, TypeReference<T> type) {
if (is == null) {
return null;
}
try {
return OBJECT_MAPPER.readValue(is, type);
} catch (JacksonException e) {
log.error("Error parsing JSON from InputStream to TypeReference: {}", e.getMessage(), e);
return null;
}
}
/**
* Write object to OutputStream as JSON
* @param source object to serialize
* @param os output stream
*/
public static void toJson(Object source, OutputStream os) {
if (source == null || os == null) {
return;
}
try {
OBJECT_MAPPER.writeValue(os, source);
} catch (JacksonException e) {
log.error("Error writing object to OutputStream as JSON: {}", e.getMessage(), e);
}
}
/**
* Convert a value from one type to another using JSON serialization/deserialization
* @param fromValue source value
* @param toValueType target type
* @return converted value or null if error
*/
public static <T> T convertValue(Object fromValue, Class<T> toValueType) {
if (fromValue == null) {
return null;
}
try {
return OBJECT_MAPPER.convertValue(fromValue, toValueType);
} catch (JacksonException e) {
log.error("Error converting value to {}: {}", toValueType.getName(), e.getMessage(), e);
return null;
}
}
}
@@ -0,0 +1,77 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.hertzbeat.common.util;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import tools.jackson.core.JacksonException;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.SerializationFeature;
import tools.jackson.dataformat.xml.XmlMapper;
/**
* xml util
*/
@Slf4j
public class XmlUtil {
private static final XmlMapper XML_MAPPER = XmlMapper.builder()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
.build();
private XmlUtil() {
}
/**
* Convert object to XML string
*/
public static String toXml(Object source) {
if (source == null) {
return null;
}
return XML_MAPPER.writeValueAsString(source);
}
/**
* Convert XML string to object
*/
public static <T> T fromXml(String xml, Class<T> clazz) {
if (StringUtils.isEmpty(xml)) {
return null;
}
return XML_MAPPER.readValue(xml, clazz);
}
/**
* Convert XML string to object with TypeReference
*/
public static <T> T fromXml(String xml, TypeReference<T> type) {
if (!StringUtils.isEmpty(xml)) {
return null;
}
try {
return XML_MAPPER.readValue(xml, type);
} catch (JacksonException e) {
log.error("Error parsing XML to TypeReference: {}", e.getMessage(), e);
return null;
}
}
}
@@ -24,7 +24,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.fasterxml.jackson.core.type.TypeReference;
import tools.jackson.core.type.TypeReference;
import java.util.Map;
import org.junit.jupiter.api.Test;
@@ -36,7 +36,7 @@ class JsonUtilTest {
@Test
void toJson() {
Map<String, String> map = Map.of("test", "pro");
assertEquals("{\"test\":\"pro\"}",
JsonUtil.toJson(map));
@@ -47,7 +47,7 @@ class JsonUtilTest {
void testFromJson() {
assertNull(JsonUtil.fromJson("", new TypeReference<>() {
}));
assertNull(JsonUtil.fromJson(null, new TypeReference<>() {
assertNull(JsonUtil.fromJson((String) null, new TypeReference<>() {
}));
assertNull(JsonUtil.fromJson(" ", new TypeReference<>() {
}));
+1 -17
View File
@@ -35,7 +35,7 @@
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat-common-core</artifactId>
</dependency>
<!-- spring -->
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -52,20 +52,9 @@
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<!-- jpa -->
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.jpa</artifactId>
<version>${eclipse-jpa.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<exclusions>
<exclusion>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- validation -->
<dependency>
@@ -78,11 +67,6 @@
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<scope>provided</scope>
</dependency>
<!-- jackson -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
</dependencies>
</project>
@@ -1,68 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.hertzbeat.common.config;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.persistence.config.SessionCustomizer;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.sessions.Session;
/**
* change column name that from upper-case to lower-case-with-underscore
*/
@Slf4j
public class EclipseLinkCustomizer implements SessionCustomizer {
@Override
public void customize(Session session) throws Exception {
for (ClassDescriptor descriptor : session.getDescriptors().values()) {
for (DatabaseMapping mapping : descriptor.getMappings()) {
if (mapping.isDirectToFieldMapping()) {
// update the column name to lower case with underscore
if (!mapping.getField().getName().equalsIgnoreCase(mapping.getAttributeName())) {
// already custom define the column name, ignore
continue;
}
// todo here i try to change column name that from upper-case to lower-case-with-underscore
// but failed, when update name, something relate exception happen more
mapping.getField().setName(convertCamelCase(mapping.getAttributeName()));
}
}
}
}
private String convertCamelCase(String camelCase) {
StringBuilder result = new StringBuilder();
if (camelCase != null && !camelCase.isEmpty()) {
result.append(Character.toLowerCase(camelCase.charAt(0)));
for (int i = 1; i < camelCase.length(); i++) {
char currentChar = camelCase.charAt(i);
if (Character.isUpperCase(currentChar)) {
result.append('_').append(Character.toLowerCase(currentChar));
} else {
result.append(currentChar);
}
}
}
return result.toString();
}
}
@@ -1,61 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.hertzbeat.common.config;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter;
import org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter;
import org.springframework.transaction.jta.JtaTransactionManager;
/**
* jpa eclipselink impl config
*/
@Configuration
@ConditionalOnProperty(prefix = "spring.datasource", name = "url")
public class EclipseLinkJpaConfiguration extends JpaBaseConfiguration {
protected EclipseLinkJpaConfiguration(DataSource dataSource, JpaProperties properties,
ObjectProvider<JtaTransactionManager> jtaTransactionManager) {
super(dataSource, properties, jtaTransactionManager);
}
@Override
protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
return new EclipseLinkJpaVendorAdapter();
}
@Override
protected Map<String, Object> getVendorProperties() {
HashMap<String, Object> map = new HashMap<>(8);
map.put(PersistenceUnitProperties.DDL_GENERATION, "create-or-extend-tables");
map.put(PersistenceUnitProperties.SESSION_CUSTOMIZER, "org.apache.hertzbeat.common.config.EclipseLinkCustomizer");
map.put(PersistenceUnitProperties.ALLOW_NATIVE_SQL_QUERIES, "true");
map.put(PersistenceUnitProperties.WEAVING, "true");
return map;
}
}
@@ -24,7 +24,6 @@ import jakarta.persistence.EntityListeners;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import org.springframework.data.annotation.CreatedBy;
@@ -55,7 +54,7 @@ public class ChatConversation {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Schema(title = "conversation title")
private String title;
@@ -78,7 +77,6 @@ public class ChatConversation {
/**
* List of messages in this conversation (one-to-many relationship)
*/
@OneToMany
@JoinColumn(name = "conversation_id")
@OneToMany(mappedBy = "conversation")
private List<ChatMessage> messages;
}
@@ -19,14 +19,16 @@ package org.apache.hertzbeat.common.entity.ai;
import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Index;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.Lob;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotBlank;
import java.time.LocalDateTime;
@@ -59,8 +61,14 @@ public class ChatMessage {
private Long id;
@Schema(title = "conversation id")
@Column(name = "conversation_id", insertable = false, updatable = false)
private Long conversationId;
@Schema(title = "conversation", hidden = true)
@ManyToOne
@JoinColumn(name = "conversation_id")
private ChatConversation conversation;
@Schema(title = "message content")
@Lob
@NotBlank
@@ -17,7 +17,7 @@
package org.apache.hertzbeat.common.entity.alerter;
import com.fasterxml.jackson.core.type.TypeReference;
import tools.jackson.core.type.TypeReference;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import java.util.HashMap;
@@ -17,7 +17,7 @@
package org.apache.hertzbeat.common.entity.manager;
import com.fasterxml.jackson.core.type.TypeReference;
import tools.jackson.core.type.TypeReference;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import java.util.List;
@@ -17,7 +17,7 @@
package org.apache.hertzbeat.common.entity.manager;
import com.fasterxml.jackson.core.type.TypeReference;
import tools.jackson.core.type.TypeReference;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import java.util.List;
@@ -17,13 +17,13 @@
package org.apache.hertzbeat.common.entity.manager;
import com.fasterxml.jackson.core.type.TypeReference;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import java.util.List;
import java.util.Map;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.springframework.stereotype.Component;
import tools.jackson.core.type.TypeReference;
/**
* json map list str converter
@@ -17,7 +17,7 @@
package org.apache.hertzbeat.common.entity.manager;
import com.fasterxml.jackson.core.type.TypeReference;
import tools.jackson.core.type.TypeReference;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import java.util.List;
@@ -17,7 +17,7 @@
package org.apache.hertzbeat.common.entity.manager;
import com.fasterxml.jackson.core.type.TypeReference;
import tools.jackson.core.type.TypeReference;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import java.util.List;
@@ -1,78 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.hertzbeat.common.util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
/**
* xml util
*/
@Slf4j
public class XmlUtil {
private static final XmlMapper XML_MAPPER = new XmlMapper();
static {
XML_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
.registerModule(new JavaTimeModule());
}
public String toXml(Object source) {
if (source == null) {
return null;
}
try {
return XML_MAPPER.writeValueAsString(source);
} catch (JsonProcessingException e) {
log.error(e.getMessage(), e);
return null;
}
}
public static <T> T fromXml(String xml, Class<T> clazz) {
if (!StringUtils.hasText(xml)) {
return null;
}
try {
return XML_MAPPER.readValue(xml, clazz);
} catch (Exception e) {
log.error(e.getMessage(), e);
return null;
}
}
public static <T> T fromXml(String xml, TypeReference<T> type) {
if (!StringUtils.hasText(xml)) {
return null;
}
try {
return XML_MAPPER.readValue(xml, type);
} catch (Exception e) {
log.error(e.getMessage(), e);
return null;
}
}
}
@@ -39,6 +39,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -71,7 +72,7 @@ public abstract class AbstractCollectE2eTest {
public void setUp() throws Exception {
// Initialize mocks
MockitoAnnotations.openMocks(this);
when(defineDao.findAll()).thenReturn(new ArrayList<>());
lenient().when(defineDao.findAll()).thenReturn(new ArrayList<>());
when(timeout.task()).thenReturn(timerJob);
when(timerJob.getJob()).thenReturn(job);
metricsCollect = new MetricsCollect(mock(Metrics.class), timeout, mock(CollectDataDispatch.class), null, List.of());
+5 -2
View File
@@ -31,8 +31,6 @@
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<okhttp.version>4.12.0</okhttp.version>
<jackson.version>2.15.2</jackson.version>
<maven.resources.plugin.version>3.3.1</maven.resources.plugin.version>
<maven.dependency.plugin.version>3.6.1</maven.dependency.plugin.version>
@@ -84,6 +82,11 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test-autoconfigure</artifactId>
<scope>test</scope>
</dependency>
<!-- JUnit 5 -->
<dependency>
@@ -28,10 +28,11 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.context.TestPropertySource;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.Wait;
@@ -44,6 +45,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -59,6 +61,10 @@ import static org.mockito.Mockito.doAnswer;
* E2E tests for periodic log alert processing.
*/
@SpringBootTest(classes = org.apache.hertzbeat.startup.HertzBeatApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(properties = {
"warehouse.store.duckdb.enabled=false",
"warehouse.store.greptime.enabled=true"
})
@Slf4j
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class LogPeriodicAlertE2eTest {
@@ -81,7 +87,7 @@ public class LogPeriodicAlertE2eTest {
AlertDefine errorCountAlertByGroup;
AlertDefine errorCountAlertByIndividual;
@SpyBean
@MockitoSpyBean
private AlarmCommonReduce alarmCommonReduce;
static GenericContainer<?> vector;
@@ -101,9 +107,7 @@ public class LogPeriodicAlertE2eTest {
@DynamicPropertySource
static void greptimeProps(DynamicPropertyRegistry r) {
// Configure GreptimeDB storage
r.add("warehouse.store.duckdb.enabled", () -> "false");
r.add("warehouse.store.greptime.enabled", () -> "true");
// Configure GreptimeDB storage endpoints (dynamic ports)
r.add("warehouse.store.greptime.http-endpoint", () -> "http://localhost:" + greptimedb.getMappedPort(GREPTIME_HTTP_PORT));
r.add("warehouse.store.greptime.grpc-endpoints", () -> "localhost:" + greptimedb.getMappedPort(GREPTIME_GRPC_PORT));
r.add("warehouse.store.greptime.username", () -> "");
@@ -111,10 +115,15 @@ public class LogPeriodicAlertE2eTest {
}
@BeforeAll
void setUpAll() {
void setUpAll() throws InterruptedException {
// Setup test alert definitions
setupTestAlertDefines();
Testcontainers.exposeHostPorts(port);
// Wait for HertzBeat to be fully ready before starting Vector
log.info("Waiting for HertzBeat to be fully ready on port {}...", port);
Thread.sleep(5000); // Give HertzBeat time to fully initialize
vector = new GenericContainer<>(DockerImageName.parse(VECTOR_IMAGE))
.withExposedPorts(VECTOR_PORT)
.withCopyFileToContainer(MountableFile.forClasspathResource("vector.yml"), VECTOR_CONFIG_PATH)
@@ -171,19 +180,22 @@ public class LogPeriodicAlertE2eTest {
await().atMost(Duration.ofSeconds(60))
.pollInterval(Duration.ofSeconds(3))
.untilAsserted(() -> assertFalse(capturedGroupAlerts.isEmpty(),
"Should have generated periodic error count group alert"));
.untilAsserted(() -> {
Optional<SingleAlert> matchedAlert = capturedGroupAlerts.stream()
.flatMap(List::stream)
.filter(alert -> alert.getLabels() != null)
.filter(alert -> String.valueOf(errorCountAlertByGroup.getId())
.equals(alert.getLabels().get(CommonConstants.LABEL_DEFINE_ID)))
.findFirst();
List<SingleAlert> groupAlerts = capturedGroupAlerts.get(0);
assertNotNull(groupAlerts, "Group alerts should not be null");
assertFalse(groupAlerts.isEmpty(), "Group alerts should not be empty");
SingleAlert anyAlert = groupAlerts.get(0);
assertEquals(CommonConstants.ALERT_STATUS_FIRING, anyAlert.getStatus(), "Alert should be in firing status");
assertNotNull(anyAlert.getLabels(), "Alert should have labels");
assertEquals(CommonConstants.ALERT_SEVERITY_CRITICAL, anyAlert.getLabels().get(CommonConstants.LABEL_ALERT_SEVERITY), "Alert should have critical severity");
assertTrue(anyAlert.getTriggerTimes() >= 1, "Alert should indicate aggregated trigger times");
assertTrue(matchedAlert.isPresent(), "Should have captured group alert from target alert define");
SingleAlert anyAlert = matchedAlert.get();
assertEquals(CommonConstants.ALERT_STATUS_FIRING, anyAlert.getStatus(), "Alert should be in firing status");
assertEquals(CommonConstants.ALERT_SEVERITY_CRITICAL,
anyAlert.getLabels().get(CommonConstants.LABEL_ALERT_SEVERITY),
"Alert should have critical severity");
assertTrue(anyAlert.getTriggerTimes() >= 1, "Alert should indicate aggregated trigger times");
});
}
/**
@@ -27,8 +27,8 @@ import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.Wait;
@@ -64,7 +64,8 @@ public class LogRealTimeAlertE2eTest {
private static final int VECTOR_PORT = 8686;
private static final String VECTOR_CONFIG_PATH = "/etc/vector/vector.yml";
private static final String ENV_HERTZBEAT_PORT = "HERTZBEAT_PORT";
private static final Duration CONTAINER_STARTUP_TIMEOUT = Duration.ofSeconds(120);
private static final Duration CONTAINER_STARTUP_TIMEOUT = Duration.ofSeconds(180);
private static final Duration TEST_WAIT_TIMEOUT = Duration.ofSeconds(120);
@LocalServerPort
private int port;
@@ -72,18 +73,23 @@ public class LogRealTimeAlertE2eTest {
private final List<SingleAlert> capturedAlerts = new ArrayList<>();
private final ArrayList<List<SingleAlert>> capturedGroupAlerts = new ArrayList<>();
@SpyBean
@MockitoSpyBean
private AlarmCommonReduce alarmCommonReduce;
static GenericContainer<?> vector;
@BeforeAll
void setUpAll() {
void setUpAll() throws InterruptedException {
// Setup test alert definitions
setupTestAlertDefines();
// Expose host ports for testcontainers
Testcontainers.exposeHostPorts(port);
// Wait for HertzBeat to be fully ready before starting Vector
log.info("Waiting for HertzBeat to be fully ready on port {}...", port);
Thread.sleep(5000); // Give HertzBeat time to fully initialize
vector = new GenericContainer<>(DockerImageName.parse(VECTOR_IMAGE))
.withExposedPorts(VECTOR_PORT)
.withCopyFileToContainer(MountableFile.forClasspathResource("vector.yml"), VECTOR_CONFIG_PATH)
@@ -107,8 +113,8 @@ public class LogRealTimeAlertE2eTest {
capturedAlerts.clear();
// Wait for real alert to be generated through AlarmCommonReduce
await().atMost(Duration.ofSeconds(60))
.pollInterval(Duration.ofSeconds(2))
await().atMost(TEST_WAIT_TIMEOUT)
.pollInterval(Duration.ofSeconds(3))
.untilAsserted(() -> assertFalse(capturedAlerts.isEmpty(),
"Should have generated at least one alert for error logs"));
@@ -64,8 +64,13 @@ public class LogIngestionE2eTest {
static GenericContainer<?> vector;
@BeforeAll
void setUpAll() {
void setUpAll() throws InterruptedException {
Testcontainers.exposeHostPorts(port);
// Wait for HertzBeat to be fully ready before starting Vector
log.info("Waiting for HertzBeat to be fully ready on port {}...", port);
Thread.sleep(5000); // Give HertzBeat time to fully initialize
vector = new GenericContainer<>(DockerImageName.parse(VECTOR_IMAGE))
.withExposedPorts(VECTOR_PORT)
.withCopyFileToContainer(MountableFile.forClasspathResource("vector.yml"), VECTOR_CONFIG_PATH)
@@ -29,6 +29,10 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.context.TestPropertySource;
import java.util.ArrayList;
import java.util.List;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.Wait;
@@ -37,8 +41,6 @@ import org.testcontainers.utility.DockerImageName;
import org.testcontainers.utility.MountableFile;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -48,6 +50,10 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
* E2E tests for GreptimeDB log storage.
*/
@SpringBootTest(classes = org.apache.hertzbeat.startup.HertzBeatApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(properties = {
"warehouse.store.duckdb.enabled=false",
"warehouse.store.greptime.enabled=true"
})
@Slf4j
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class GreptimeLogStorageE2eTest {
@@ -86,8 +92,6 @@ public class GreptimeLogStorageE2eTest {
@DynamicPropertySource
static void greptimeProps(DynamicPropertyRegistry r) {
r.add("warehouse.store.duckdb.enabled", () -> "false");
r.add("warehouse.store.greptime.enabled", () -> "true");
r.add("warehouse.store.greptime.http-endpoint", () -> "http://localhost:" + greptimedb.getMappedPort(GREPTIME_HTTP_PORT));
r.add("warehouse.store.greptime.grpc-endpoints", () -> "localhost:" + greptimedb.getMappedPort(GREPTIME_GRPC_PORT));
r.add("warehouse.store.greptime.username", () -> "");
@@ -96,10 +100,14 @@ public class GreptimeLogStorageE2eTest {
@BeforeAll
void setUpAll() {
void setUpAll() throws InterruptedException {
// Expose host ports for testcontainers
Testcontainers.exposeHostPorts(port);
// Wait for HertzBeat to be fully ready before starting Vector
log.info("Waiting for HertzBeat to be fully ready on port {}...", port);
Thread.sleep(5000); // Give HertzBeat time to fully initialize
vector = new GenericContainer<>(DockerImageName.parse(VECTOR_IMAGE))
.withExposedPorts(VECTOR_PORT)
.withCopyFileToContainer(MountableFile.forClasspathResource("vector.yml"), VECTOR_CONFIG_PATH)
@@ -114,9 +122,10 @@ public class GreptimeLogStorageE2eTest {
@Test
void testLogStorageToGreptimeDb() {
log.info("GreptimeDbDataStorage serverAvailable: {}", greptimeDbDataStorage.isServerAvailable());
List<LogEntry> capturedLogs = new ArrayList<>();
// Wait for Vector to generate and send logs to HertzBeat
await().atMost(Duration.ofSeconds(30))
.pollInterval(Duration.ofSeconds(3))
@@ -131,7 +140,7 @@ public class GreptimeLogStorageE2eTest {
Thread.currentThread().interrupt();
throw new RuntimeException("Test interrupted", e);
}
// Assert that we have captured at least some logs
assertFalse(capturedLogs.isEmpty(), "Should have captured at least one log entry");
});
@@ -142,13 +151,25 @@ public class GreptimeLogStorageE2eTest {
assertNotNull(firstLog, "First log should not be null");
assertNotNull(firstLog.getBody(), "Log body should not be null");
assertNotNull(firstLog.getSeverityText(), "Severity text should not be null");
// Directly write logs to GreptimeDB to test storage functionality
log.info("Directly writing {} captured logs to GreptimeDB", capturedLogs.size());
greptimeDbDataStorage.saveLogDataBatch(capturedLogs);
// Give some time for the write to complete
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// Additional wait to ensure logs are persisted to GreptimeDB
await().atMost(Duration.ofSeconds(30))
.pollInterval(Duration.ofSeconds(2))
.untilAsserted(() -> {
// Query GreptimeDB directly to verify data persistence
List<LogEntry> storedLogs = queryStoredLogs();
log.info("Queried {} logs from GreptimeDB", storedLogs.size());
assertFalse(storedLogs.isEmpty(), "Should have logs stored in GreptimeDB");
});
}
@@ -157,10 +178,10 @@ public class GreptimeLogStorageE2eTest {
* Helper method to query stored logs directly from GreptimeDB
*/
private List<LogEntry> queryStoredLogs() {
long endTime = System.currentTimeMillis();
long startTime = endTime - Duration.ofMinutes(5).toMillis(); // Look back 5 minutes
return greptimeDbDataStorage.queryLogsByMultipleConditions(
startTime, endTime, null, null, null, null, null);
// Query without time condition to verify data exists
List<LogEntry> result = greptimeDbDataStorage.queryLogsByMultipleConditions(
null, null, null, null, null, null, null);
log.info("queryLogsByMultipleConditions returned {} entries", result.size());
return result;
}
}
@@ -95,8 +95,8 @@ sinks:
type: console
encoding:
codec: json
# Send to HertzBeat
# Send to HertzBeat with increased timeout and retry
emit_syslog:
inputs: [ "remap_syslog" ]
type: opentelemetry
@@ -114,4 +114,13 @@ sinks:
strategy: basic
user: admin
password: hertzbeat
# Increase timeout and retry settings for stability in CI environments
request:
timeout_secs: 60
retry_attempts: 10
retry_initial_backoff_secs: 2
retry_max_duration_secs: 120
# Batch settings for better throughput
batch:
max_bytes: 524288
timeout_secs: 5
+5
View File
@@ -42,6 +42,11 @@
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat-warehouse</artifactId>
</dependency>
<!-- hertzbeat common-core -->
<dependency>
<groupId>org.apache.hertzbeat</groupId>
<artifactId>hertzbeat-common-core</artifactId>
</dependency>
<!-- jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -17,8 +17,7 @@
package org.apache.hertzbeat.grafana.service;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import tools.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
@@ -77,8 +76,6 @@ public class DashboardService {
@Autowired
private GeneralConfigDao generalConfigDao;
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
/**
* Creates or updates a dashboard in Grafana.
* The "id" field will be removed from the dashboard JSON before sending
@@ -275,7 +272,10 @@ public class DashboardService {
try {
GeneralConfig config = generalConfigDao.findByType(GeneralConfigTypeEnum.system.name());
if (config != null && config.getContent() != null) {
JsonNode root = OBJECT_MAPPER.readTree(config.getContent());
JsonNode root = JsonUtil.fromJson(config.getContent());
if (root == null) {
return ThemeEnum.LIGHT.getValue();
}
JsonNode node = root.get("theme");
if (node != null && !node.isNull()) {
return ThemeEnum.convert(node.asText());
@@ -23,7 +23,7 @@ import static org.apache.hertzbeat.grafana.common.GrafanaConstants.CREATE_SERVIC
import static org.apache.hertzbeat.grafana.common.GrafanaConstants.CREATE_SERVICE_TOKEN_API;
import static org.apache.hertzbeat.grafana.common.GrafanaConstants.GET_SERVICE_ACCOUNTS_API;
import static org.apache.hertzbeat.grafana.common.GrafanaConstants.GRAFANA_CONFIG;
import com.fasterxml.jackson.databind.JsonNode;
import tools.jackson.databind.JsonNode;
import jakarta.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
+5 -5
View File
@@ -27,7 +27,7 @@
<name>${project.artifactId}</name>
<packaging>jar</packaging>
<dependencies>
<!-- common-spring (includes common-core) -->
<dependency>
@@ -138,10 +138,10 @@
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- database migration -->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-flyway</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId>
@@ -0,0 +1,147 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 com.usthe.sureness.configuration;
import com.usthe.sureness.mgt.SecurityManager;
import com.usthe.sureness.processor.exception.DisabledAccountException;
import com.usthe.sureness.processor.exception.ExcessiveAttemptsException;
import com.usthe.sureness.processor.exception.ExpiredCredentialsException;
import com.usthe.sureness.processor.exception.IncorrectCredentialsException;
import com.usthe.sureness.processor.exception.NeedDigestInfoException;
import com.usthe.sureness.processor.exception.UnauthorizedException;
import com.usthe.sureness.processor.exception.UnknownAccountException;
import com.usthe.sureness.subject.SubjectSum;
import com.usthe.sureness.util.SurenessContextHolder;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import java.io.IOException;
import java.io.PrintWriter;
/**
* Override the default servlet filter
*/
public class SurenessJakartaServletFilter implements Filter {
private final SecurityManager securityManager;
public SurenessJakartaServletFilter(SecurityManager securityManager) {
this.securityManager = securityManager;
}
/** logger **/
private static final Logger logger = LoggerFactory.getLogger(SurenessJakartaServletFilter.class);
private static final String UPGRADE = "Upgrade";
private static final String WEBSOCKET = "websocket";
@Override
public void init(FilterConfig filterConfig) {
logger.info("servlet surenessFilter initialized");
}
@Override
public void destroy() {
logger.info("servlet surenessFilter destroyed");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain)
throws IOException, ServletException {
try {
SubjectSum subject = securityManager.checkIn(servletRequest);
// You can consider using SurenessContextHolder to bind subject in threadLocal
// if bind, please remove it when end
if (subject != null) {
SurenessContextHolder.bindSubject(subject);
}
} catch (IncorrectCredentialsException | UnknownAccountException | ExpiredCredentialsException e1) {
logger.debug("this request account info is illegal, {}", e1.getMessage());
responseWrite(ResponseEntity
.status(HttpStatus.UNAUTHORIZED)
.body("Username or password is incorrect or token expired"), servletResponse);
return;
} catch (DisabledAccountException | ExcessiveAttemptsException e2) {
logger.debug("the account is disabled, {}", e2.getMessage());
responseWrite(ResponseEntity
.status(HttpStatus.UNAUTHORIZED).body("Account is disabled"), servletResponse);
return;
} catch (NeedDigestInfoException e3) {
logger.debug("you should try once again with digest auth information");
responseWrite(ResponseEntity
.status(HttpStatus.UNAUTHORIZED)
.header("WWW-Authenticate", e3.getAuthenticate()).build(), servletResponse);
return;
} catch (UnauthorizedException e4) {
logger.debug("this account can not access this resource, {}", e4.getMessage());
responseWrite(ResponseEntity
.status(HttpStatus.FORBIDDEN)
.body("This account has no permission to access this resource"), servletResponse);
return;
} catch (RuntimeException e) {
logger.error("other exception happen: ", e);
responseWrite(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(),
servletResponse);
return;
}
try {
// if ok, doFilter and add subject in request
filterChain.doFilter(servletRequest, servletResponse);
} finally {
int statusCode = ((HttpServletResponse) servletResponse).getStatus();
String upgrade = ((HttpServletResponse) servletResponse).getHeader(UPGRADE);
if (statusCode != HttpStatus.SWITCHING_PROTOCOLS.value() || !WEBSOCKET.equals(upgrade)) {
SurenessContextHolder.clear();
}
}
}
/**
* write response json data
* @param content content
* @param response response
*/
private static void responseWrite(ResponseEntity content, ServletResponse response) {
response.setCharacterEncoding("UTF-8");
((HttpServletResponse) response).setStatus(content.getStatusCode().value());
content.getHeaders().forEach((key, value) ->
((HttpServletResponse) response).addHeader(key, value.get(0)));
try (PrintWriter printWriter = response.getWriter()) {
if (content.getBody() != null) {
printWriter.write(content.getBody().toString());
} else {
printWriter.flush();
}
} catch (IOException e) {
logger.error("responseWrite response error: ", e);
}
}
}
@@ -1,47 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.hertzbeat.manager.component.listener;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.support.event.SystemConfigChangeEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
/**
* Listener for config Jackson timezone
*/
@Slf4j
@Component
public class TimeZoneListener {
@Resource
private ObjectMapper objectMapper;
@EventListener(SystemConfigChangeEvent.class)
public void onEvent(SystemConfigChangeEvent event) {
log.info("{} receive system config change event: {}.", this.getClass().getName(), event.getSource());
final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX");
simpleDateFormat.setTimeZone(TimeZone.getDefault());
objectMapper.setTimeZone(TimeZone.getDefault())
.setDateFormat(simpleDateFormat);
}
}
@@ -17,7 +17,7 @@
package org.apache.hertzbeat.manager.component.validator.impl;
import com.fasterxml.jackson.core.type.TypeReference;
import tools.jackson.core.type.TypeReference;
import org.apache.hertzbeat.common.entity.manager.Param;
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
import org.apache.hertzbeat.common.util.JsonUtil;
@@ -26,7 +26,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider;
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders;
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
import org.springframework.boot.webmvc.autoconfigure.error.ErrorViewResolver;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
@@ -6,7 +6,7 @@
* (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
* 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,
@@ -17,7 +17,6 @@
package org.apache.hertzbeat.manager.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.usthe.sureness.util.JsonWebTokenUtil;
import jakarta.annotation.Resource;
import java.security.SecureRandom;
@@ -29,6 +28,7 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.entity.manager.GeneralConfig;
import org.apache.hertzbeat.common.util.AesUtil;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.apache.hertzbeat.common.util.TimeZoneUtil;
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
import org.apache.hertzbeat.manager.pojo.dto.MuteConfig;
@@ -56,9 +56,9 @@ public class ConfigInitializer implements SmartLifecycle {
private boolean running = false;
private static final String DEFAULT_JWT_SECRET = "CyaFv0bwq2Eik0jdrKUtsA6bx3sDJeFV643R "
+ "LnfKefTjsIfJLBa2YkhEqEGtcHDTNe4CU6+9 "
+ "8tVt4bisXQ13rbN0oxhUZR73M6EByXIO+SV5 "
+ "dKhaX0csgOCTlCxq20yhmUea6H6JIpSE2Rwp";
+ "LnfKefTjsIfJLBa2YkhEqEGtcHDTNe4CU6+9 "
+ "8tVt4bisXQ13rbN0oxhUZR73M6EByXIO+SV5 "
+ "dKhaX0csgOCTlCxq20yhmUea6H6JIpSE2Rwp";
@Value("${sureness.jwt.secret:" + DEFAULT_JWT_SECRET + "}")
private String currentJwtSecret;
@@ -84,9 +84,6 @@ public class ConfigInitializer implements SmartLifecycle {
@Resource
protected GeneralConfigDao generalConfigDao;
@Resource
protected ObjectMapper objectMapper;
@SneakyThrows
public void initConfig() {
// for system config
@@ -96,19 +93,17 @@ public class ConfigInitializer implements SmartLifecycle {
final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX");
simpleDateFormat.setTimeZone(TimeZone.getDefault());
objectMapper.setTimeZone(TimeZone.getDefault())
.setDateFormat(simpleDateFormat);
} else {
// init system config data
systemConfig = SystemConfig.builder().timeZoneId(TimeZone.getDefault().getID()).theme("default")
.locale(Locale.getDefault().getLanguage() + CommonConstants.LOCALE_SEPARATOR
+ Locale.getDefault().getCountry())
.build();
String contentJson = objectMapper.writeValueAsString(systemConfig);
.locale(Locale.getDefault().getLanguage() + CommonConstants.LOCALE_SEPARATOR
+ Locale.getDefault().getCountry())
.build();
String contentJson = JsonUtil.toJson(systemConfig);
GeneralConfig generalConfig2Save = GeneralConfig.builder()
.type(systemGeneralConfigService.type())
.content(contentJson)
.build();
.type(systemGeneralConfigService.type())
.content(contentJson)
.build();
generalConfigDao.save(generalConfig2Save);
}
// for template config, flush the template config in db to memory
@@ -19,28 +19,35 @@ package org.apache.hertzbeat.manager.config;
import org.flywaydb.core.Flyway;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.flyway.FlywayMigrationInitializer;
import org.springframework.boot.autoconfigure.flyway.FlywayProperties;
import org.springframework.boot.flyway.autoconfigure.FlywayMigrationInitializer;
import org.springframework.boot.flyway.autoconfigure.FlywayProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
/**
* flyway database migration config
* Flyway database migration config.
* Delays Flyway execution until after Hibernate has created/updated the schema.
*/
@Configuration
@ConditionalOnProperty(prefix = "spring.flyway", name = "enabled", havingValue = "true")
public class FlywayConfiguration {
/**
* Disable the default FlywayMigrationInitializer by providing an empty callback.
*/
@Bean
public FlywayMigrationInitializer flywayInitializer(Flyway flyway) {
return new FlywayMigrationInitializer(flyway, (f) -> {
// Empty callback - we'll run migrations manually after Hibernate
});
}
static class Dummy {
}
/**
* Delayed Flyway migration that runs after EntityManagerFactory is initialized.
* This ensures Hibernate's ddl-auto runs first to create/update tables,
* then Flyway can perform additional migrations if needed.
*/
@Bean
@DependsOn("entityManagerFactory")
Dummy delayedFlywayInitializer(Flyway flyway, FlywayProperties flywayProperties) {
@@ -49,4 +56,7 @@ public class FlywayConfiguration {
}
return new Dummy();
}
static class Dummy {
}
}
@@ -6,7 +6,7 @@
* (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
* 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,
@@ -17,39 +17,32 @@
package org.apache.hertzbeat.manager.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import tools.jackson.databind.DeserializationFeature;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
/**
* jackson config
* Jackson config.
*/
@Slf4j
@Configuration
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() {
public JsonMapperBuilderCustomizer jsonMapperBuilderCustomizer() {
return builder -> {
JavaTimeModule javaTimeModule = new JavaTimeModule();
final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX");
final String dateTimeFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSX";
final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateTimeFormat);
simpleDateFormat.setTimeZone(TimeZone.getDefault());
builder.modules(javaTimeModule)
.timeZone(TimeZone.getDefault())
.dateFormat(simpleDateFormat);
builder.defaultTimeZone(TimeZone.getDefault());
builder.defaultDateFormat(simpleDateFormat);
builder.disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES);
builder.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
};
}
@Bean
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
return builder.build();
}
}
@@ -6,7 +6,7 @@
* (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
* 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,
@@ -17,24 +17,25 @@
package org.apache.hertzbeat.manager.config;
import java.net.http.HttpClient;
import java.time.Duration;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import org.apache.hertzbeat.common.constants.NetworkConstants;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.http.client.JdkClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
/**
* restTemplate config
* todo thread pool
* RestTemplate configuration using JDK native HttpClient for Spring 7.0.
*/
@Configuration
public class RestTemplateConfig {
/**
* Create RestTemplate with JDK native request factory and custom interceptors.
*/
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
RestTemplate restTemplate = new RestTemplate(factory);
@@ -43,19 +44,17 @@ public class RestTemplateConfig {
}
@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
public ClientHttpRequestFactory clientHttpRequestFactory() {
HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(NetworkConstants.HttpClientConstants.CONNECT_TIME_OUT))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
return new OkHttp3ClientHttpRequestFactory(
new OkHttpClient.Builder()
.readTimeout(NetworkConstants.HttpClientConstants.READ_TIME_OUT, TimeUnit.SECONDS)
.writeTimeout(NetworkConstants.HttpClientConstants.WRITE_TIME_OUT, TimeUnit.SECONDS)
.connectTimeout(NetworkConstants.HttpClientConstants.CONNECT_TIME_OUT, TimeUnit.SECONDS)
.connectionPool(new ConnectionPool(
NetworkConstants.HttpClientConstants.MAX_IDLE_CONNECTIONS,
NetworkConstants.HttpClientConstants.KEEP_ALIVE_TIMEOUT,
TimeUnit.SECONDS)
).build()
);
JdkClientHttpRequestFactory factory = new JdkClientHttpRequestFactory(httpClient);
factory.setReadTimeout(Duration.ofSeconds(NetworkConstants.HttpClientConstants.READ_TIME_OUT));
return factory;
}
}
@@ -17,14 +17,13 @@
package org.apache.hertzbeat.manager.service.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.entity.manager.GeneralConfig;
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
import org.apache.hertzbeat.base.service.GeneralConfigService;
import org.apache.hertzbeat.common.entity.manager.GeneralConfig;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.springframework.transaction.annotation.Transactional;
import tools.jackson.core.type.TypeReference;
/**
* <p>Abstract implementation of GeneralConfigService, providing CRUD operations for configurations.</p>
@@ -34,61 +33,35 @@ abstract class AbstractGeneralConfigServiceImpl<T> implements GeneralConfigServi
protected final GeneralConfigDao generalConfigDao;
protected final ObjectMapper objectMapper;
/**
* <p>Constructor, passing in GeneralConfigDao, ObjectMapper and type.</p>
* @param generalConfigDao Dao object
* @param objectMapper JSON tool object
*/
protected AbstractGeneralConfigServiceImpl(GeneralConfigDao generalConfigDao, ObjectMapper objectMapper) {
protected AbstractGeneralConfigServiceImpl(GeneralConfigDao generalConfigDao) {
this.generalConfigDao = generalConfigDao;
this.objectMapper = objectMapper;
}
/**
* <p>Save a configuration.</p>
* @param config need to save configuration object
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void saveConfig(T config) {
try {
String contentJson = objectMapper.writeValueAsString(config);
GeneralConfig generalConfig2Save = GeneralConfig.builder()
.type(type())
.content(contentJson)
.build();
generalConfigDao.save(generalConfig2Save);
log.info("Configuration saved successfully");
handler(getConfig());
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Configuration saved failed: " + e.getMessage());
String contentJson = JsonUtil.toJson(config);
if (contentJson == null) {
log.error("Failed to serialize configuration for type {}", type());
throw new RuntimeException("Save config failed: serialization error");
}
GeneralConfig generalConfig2Save = GeneralConfig.builder()
.type(type())
.content(contentJson)
.build();
generalConfigDao.save(generalConfig2Save);
log.info("Configuration of type {} saved successfully", type());
handler(getConfig());
}
/**
* <p>Get a configuration.</p>
* @return query configuration object
*/
@Override
public T getConfig() {
GeneralConfig generalConfig = generalConfigDao.findByType(type());
if (generalConfig == null) {
return null;
}
try {
return objectMapper.readValue(generalConfig.getContent(), getTypeReference());
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Get configuration failed: " + e.getMessage());
}
return JsonUtil.fromJson(generalConfig.getContent(), getTypeReference());
}
/**
* <p>Get TypeReference object of configuration type.</p>
* @return TypeReference object
*/
protected abstract TypeReference<T> getTypeReference();
}
@@ -6,7 +6,7 @@
* (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
* 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,
@@ -32,7 +32,6 @@ import org.apache.hertzbeat.common.entity.manager.Param;
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.apache.hertzbeat.common.support.SpringContextHolder;
import org.apache.hertzbeat.common.support.exception.CommonException;
import org.apache.hertzbeat.common.util.CommonUtil;
import org.apache.hertzbeat.common.util.HertzBeatKeywordsUtil;
import org.apache.hertzbeat.common.util.JexlCheckerUtil;
@@ -48,6 +47,7 @@ import org.apache.hertzbeat.manager.service.MonitorService;
import org.apache.hertzbeat.manager.service.ObjectStoreService;
import org.apache.hertzbeat.warehouse.service.WarehouseService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.event.EventListener;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
@@ -57,7 +57,6 @@ import org.springframework.util.Assert;
import org.springframework.util.StreamUtils;
import org.yaml.snakeyaml.Yaml;
import javax.annotation.Resource;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -89,26 +88,31 @@ public class AppServiceImpl implements AppService, InitializingBean {
private static final String PUSH_PROTOCOL_METRICS_NAME = "metrics";
@Resource
private MonitorDao monitorDao;
@Resource
private ObjectStoreConfigServiceImpl objectStoreConfigService;
@Resource
private ParamDao paramDao;
@Resource
private DefineDao defineDao;
@Resource
private WarehouseService warehouseService;
private final MonitorDao monitorDao;
private final ObjectStoreConfigServiceImpl objectStoreConfigService;
private final ParamDao paramDao;
private final DefineDao defineDao;
private final WarehouseService warehouseService;
private final Map<String, Job> appDefines = new ConcurrentHashMap<>();
private AppDefineStore appDefineStore;
private final AppDefineStore jarAppDefineStore = new JarAppDefineStoreImpl();
/**
* warehouseService is marked @Lazy to prevent potential circular dependencies.
*/
public AppServiceImpl(MonitorDao monitorDao,
ObjectStoreConfigServiceImpl objectStoreConfigService,
ParamDao paramDao,
DefineDao defineDao,
@Lazy WarehouseService warehouseService) {
this.monitorDao = monitorDao;
this.objectStoreConfigService = objectStoreConfigService;
this.paramDao = paramDao;
this.defineDao = defineDao;
this.warehouseService = warehouseService;
}
@Override
public List<ParamDefine> getAppParamDefines(String app) {
if (StringUtils.isNotBlank(app)){
@@ -132,8 +136,8 @@ public class AppServiceImpl implements AppService, InitializingBean {
if (PUSH_PROTOCOL_METRICS_NAME.equals(metric.getName())) {
List<Param> params = paramDao.findParamsByMonitorId(monitorId);
List<Configmap> configmaps = params.stream()
.map(param -> new Configmap(param.getField(), param.getParamValue(),
param.getType())).toList();
.map(param -> new Configmap(param.getField(), param.getParamValue(),
param.getType())).toList();
Map<String, Configmap> configmap = configmaps.stream().collect(Collectors.toMap(Configmap::getKey, item -> item, (key1, key2) -> key1));
CollectUtil.replaceFieldsForPushStyleMonitor(metric, configmap);
metricsTmp.add(metric);
@@ -145,7 +149,6 @@ public class AppServiceImpl implements AppService, InitializingBean {
@Override
public Job getAutoGenerateDynamicDefine(Long monitorId) {
// todo now only for prometheus
Job job = getAppDefine(DispatchConstants.PROTOCOL_PROMETHEUS);
List<CollectRep.MetricsData> metricsDataList = warehouseService.queryMonitorMetricsData(monitorId);
Metrics tmpMetrics = job.getMetrics().get(0);
@@ -153,18 +156,18 @@ public class AppServiceImpl implements AppService, InitializingBean {
for (CollectRep.MetricsData metricsData : metricsDataList) {
List<Metrics.Field> fields = metricsData.getFields().stream().map(item ->
Metrics.Field.builder()
.field(item.getName())
.type((byte) item.getType())
.label(item.getLabel())
.unit(item.getUnit())
.build())
.collect(Collectors.toList());
.field(item.getName())
.type((byte) item.getType())
.label(item.getLabel())
.unit(item.getUnit())
.build())
.collect(Collectors.toList());
Metrics metrics = Metrics.builder()
.visible(true)
.name(metricsData.getMetrics())
.fields(fields)
.prometheus(tmpMetrics.getPrometheus())
.build();
.visible(true)
.name(metricsData.getMetrics())
.fields(fields)
.prometheus(tmpMetrics.getPrometheus())
.build();
metricsList.add(metrics);
}
job.setMetrics(metricsList);
@@ -203,7 +206,7 @@ public class AppServiceImpl implements AppService, InitializingBean {
metricNames.addAll(appDefine.getMetrics().stream().map(Metrics::getName).toList());
} else {
appDefines.forEach((k, v) ->
metricNames.addAll(v.getMetrics().stream().map(Metrics::getName).toList()));
metricNames.addAll(v.getMetrics().stream().map(Metrics::getName).toList()));
}
return metricNames;
}
@@ -283,7 +286,6 @@ public class AppServiceImpl implements AppService, InitializingBean {
public List<Hierarchy> getAppHierarchy(String app, String lang) {
LinkedList<Hierarchy> hierarchies = new LinkedList<>();
Job job = appDefines.get(app.toLowerCase());
// TODO temporarily filter out push to solve the front-end problem, and open it after the subsequent design optimization
if (DispatchConstants.PROTOCOL_PUSH.equalsIgnoreCase(job.getApp())) {
return hierarchies;
}
@@ -313,17 +315,16 @@ public class AppServiceImpl implements AppService, InitializingBean {
hierarchyMetric.setValue(metricsData.getMetrics());
hierarchyMetric.setLabel(metricsData.getMetrics());
List<Hierarchy> hierarchyFieldList = metricsData.getFields().stream()
.map(item -> {
var hierarchyField = new Hierarchy();
hierarchyField.setValue(item.getName());
hierarchyField.setLabel(item.getName());
hierarchyField.setIsLeaf(true);
hierarchyField.setType((byte) item.getType());
hierarchyField.setUnit(item.getUnit());
return hierarchyField;
}).collect(Collectors.toList());
.map(item -> {
var hierarchyField = new Hierarchy();
hierarchyField.setValue(item.getName());
hierarchyField.setLabel(item.getName());
hierarchyField.setIsLeaf(true);
hierarchyField.setType((byte) item.getType());
hierarchyField.setUnit(item.getUnit());
return hierarchyField;
}).collect(Collectors.toList());
hierarchyMetric.setChildren(hierarchyFieldList);
// combine Hierarchy Metrics
combineHierarchyMetrics(hierarchyMetricList, hierarchyMetric);
}
}
@@ -344,7 +345,6 @@ public class AppServiceImpl implements AppService, InitializingBean {
var metricI18nName = CommonUtil.getLangMappingValueFromI18nMap(lang, field.getI18n());
hierarchyField.setLabel(metricI18nName != null ? metricI18nName : field.getField());
hierarchyField.setIsLeaf(true);
// for metric
hierarchyField.setType(field.getType());
hierarchyField.setUnit(field.getUnit());
hierarchyFieldList.add(hierarchyField);
@@ -361,15 +361,15 @@ public class AppServiceImpl implements AppService, InitializingBean {
private void combineHierarchyMetrics(List<Hierarchy> hierarchyMetricList, Hierarchy hierarchyMetric) {
Optional<Hierarchy> preHierarchyOptional = hierarchyMetricList.stream()
.filter(item -> item.getValue().equals(hierarchyMetric.getValue()))
.findFirst();
.filter(item -> item.getValue().equals(hierarchyMetric.getValue()))
.findFirst();
if (preHierarchyOptional.isPresent()) {
Hierarchy preHierarchy = preHierarchyOptional.get();
List<Hierarchy> children = preHierarchy.getChildren();
Set<String> childrenKey = children.stream()
.map(Hierarchy::getValue)
.collect(Collectors.toSet());
.map(Hierarchy::getValue)
.collect(Collectors.toSet());
for (Hierarchy child : hierarchyMetric.getChildren()) {
if (!childrenKey.contains(child.getValue())) {
@@ -408,18 +408,14 @@ public class AppServiceImpl implements AppService, InitializingBean {
log.error(e.getMessage());
throw new IllegalArgumentException("parse yml error: " + e.getMessage());
}
// app params verify
verifyDefineAppContent(app, isModify);
appDefineStore.save(app.getApp(), ymlContent);
// get and reset hide value
Job originalJob = appDefines.get(app.getApp().toLowerCase());
if (Objects.nonNull(originalJob)) {
boolean hide = originalJob.isHide();
app.setHide(hide);
}
appDefines.put(app.getApp().toLowerCase(), app);
// resolve: after the template is modified, all monitoring instances of the same type of template need to be reissued in the task status
SpringContextHolder.getBean(MonitorService.class).updateAppCollectJob(app);
}
@@ -442,7 +438,7 @@ public class AppServiceImpl implements AppService, InitializingBean {
}
if (!isModify) {
Assert.isNull(appDefines.get(app.getApp().toLowerCase()),
"monitoring template name " + app.getApp() + " already exists.");
"monitoring template name " + app.getApp() + " already exists.");
}
Set<String> fieldsSet = new HashSet<>(16);
for (Metrics metrics : app.getMetrics()) {
@@ -453,20 +449,20 @@ public class AppServiceImpl implements AppService, InitializingBean {
CommonUtil.validDefineI18n(field.getI18n(), metrics.getName() + " metric " + field.getField() + " field");
HertzBeatKeywordsUtil.verifyKeywords(field.getField());
if (fieldsSet.contains(field.getField())) {
throw new IllegalArgumentException(app.getApp() + " " + metrics.getName() + " "
+ field.getField() + " can not duplicated.");
throw new IllegalArgumentException(app.getApp() + " " + metrics.getName() + " "
+ field.getField() + " can not duplicated.");
}
if (JexlCheckerUtil.verifyKeywords(field.getField())) {
throw new IllegalArgumentException(app.getApp() + " " + metrics.getName() + " "
+ field.getField() + " prohibited keywords.");
+ field.getField() + " prohibited keywords.");
}
if (JexlCheckerUtil.verifyStartCharacter(field.getField())) {
throw new IllegalArgumentException(app.getApp() + " " + metrics.getName() + " "
+ field.getField() + " illegal start character.");
+ field.getField() + " illegal start character.");
}
if (JexlCheckerUtil.verifySpaces(field.getField())) {
throw new IllegalArgumentException(app.getApp() + " " + metrics.getName() + " "
+ field.getField() + " no spaces allowed.");
+ field.getField() + " no spaces allowed.");
}
fieldsSet.add(field.getField());
}
@@ -475,7 +471,6 @@ public class AppServiceImpl implements AppService, InitializingBean {
@Override
public void deleteMonitorDefine(String app) {
// if app has monitors now, delete failed
var monitors = monitorDao.findMonitorsByAppEquals(app);
if (monitors != null && !monitors.isEmpty()) {
throw new IllegalArgumentException("Can not delete define which has monitoring instances.");
@@ -508,6 +503,7 @@ public class AppServiceImpl implements AppService, InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
// Guaranteed to be non-null due to constructor injection
var objectStoreConfig = objectStoreConfigService.getConfig();
refreshStore(objectStoreConfig);
}
@@ -517,11 +513,6 @@ public class AppServiceImpl implements AppService, InitializingBean {
refreshStore(event.getConfig());
}
/**
* flush config store
*
* @param objectStoreConfig file service configuration
*/
private void refreshStore(ObjectStoreDTO<?> objectStoreConfig) {
if (objectStoreConfig == null) {
appDefineStore = new DatabaseAppDefineStoreImpl();
@@ -535,7 +526,6 @@ public class AppServiceImpl implements AppService, InitializingBean {
}
}
jarAppDefineStore.loadAppDefines();
// merge define yml files inside jars
appDefineStore.loadAppDefines();
}
@@ -561,7 +551,6 @@ public class AppServiceImpl implements AppService, InitializingBean {
}
private class JarAppDefineStoreImpl implements AppDefineStore {
@Override
public boolean loadAppDefines() {
try {
@@ -575,7 +564,6 @@ public class AppServiceImpl implements AppService, InitializingBean {
appDefines.put(app.getApp().toLowerCase(), app);
} catch (IOException e) {
log.error(e.getMessage(), e);
log.error("Ignore this template file: {}.", resource.getFilename());
}
}
return true;
@@ -587,8 +575,6 @@ public class AppServiceImpl implements AppService, InitializingBean {
@Override
public String loadAppDefine(String app) {
// load define app yml in jar
log.info("load define app yml in internal jar");
var resolver = new PathMatchingResourcePatternResolver();
var resource = resolver.getResource("classpath:define/app-" + app + ".yml");
try (var inputStream = resource.getInputStream()) {
@@ -606,51 +592,25 @@ public class AppServiceImpl implements AppService, InitializingBean {
@Override
public void delete(String app) {
throw new UnsupportedOperationException("define yml inside jars cannot be deleted");
throw new UnsupportedOperationException();
}
}
private class LocalFileAppDefineStoreImpl implements AppDefineStore {
@Override
public boolean loadAppDefines() {
var rootUrl = this.getClass().getClassLoader().getResource("");
if (rootUrl == null) {
return false;
}
var classpath = rootUrl.getPath();
var defineAppPath = classpath + "define";
var directory = new File(defineAppPath);
if (!directory.exists() || directory.listFiles() == null) {
rootUrl = this.getClass().getResource(File.separator);
if (rootUrl == null) {
return false;
}
classpath = rootUrl.getPath();
defineAppPath = classpath + "define";
directory = new File(defineAppPath);
if (!directory.exists() || directory.listFiles() == null) {
return false;
}
}
log.info("load define path {}", defineAppPath);
if (rootUrl == null) return false;
var directory = new File(rootUrl.getPath() + "define");
if (!directory.exists()) return false;
Yaml yaml = new Yaml();
for (var appFile : Objects.requireNonNull(directory.listFiles())) {
if (appFile.exists() && appFile.isFile()) {
if (appFile.isHidden()
|| (!appFile.getName().endsWith("yml") && !appFile.getName().endsWith("yaml"))) {
log.error("Ignore this template file: {}.", appFile.getName());
continue;
}
try (var fileInputStream = new FileInputStream(appFile)) {
var app = yaml.loadAs(fileInputStream, Job.class);
if (app != null) {
appDefines.put(app.getApp().toLowerCase(), app);
}
} catch (IOException e) {
log.error(e.getMessage(), e);
log.error("Ignore this template file: {}.", appFile.getName());
if (appFile.isFile() && (appFile.getName().endsWith("yml") || appFile.getName().endsWith("yaml"))) {
try (var is = new FileInputStream(appFile)) {
var app = yaml.loadAs(is, Job.class);
if (app != null) appDefines.put(app.getApp().toLowerCase(), app);
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
@@ -659,132 +619,93 @@ public class AppServiceImpl implements AppService, InitializingBean {
@Override
public String loadAppDefine(String app) {
var classpath = Objects.requireNonNull(this.getClass().getClassLoader().getResource("")).getPath();
var defineAppPath = classpath + "define" + File.separator + "app-" + app + ".yml";
var defineAppFile = new File(defineAppPath);
if (defineAppFile.exists() && defineAppFile.isFile()) {
log.info("load {} define app yml in file: {}", app, defineAppPath);
try {
return FileUtils.readFileToString(defineAppFile, StandardCharsets.UTF_8);
} catch (Exception e) {
log.error(e.getMessage());
}
var rootUrl = this.getClass().getClassLoader().getResource("");
if (rootUrl == null) return null;
var file = new File(rootUrl.getPath() + "define" + File.separator + "app-" + app + ".yml");
try {
return file.exists() ? FileUtils.readFileToString(file, StandardCharsets.UTF_8) : null;
} catch (Exception e) {
return null;
}
return null;
}
@Override
public void save(String app, String ymlContent) {
var classpath = Objects.requireNonNull(this.getClass().getClassLoader().getResource("")).getPath();
var defineAppPath = classpath + "define" + File.separator + "app-" + app + ".yml";
var defineAppFile = new File(defineAppPath);
var rootUrl = this.getClass().getClassLoader().getResource("");
if (rootUrl == null) return;
var file = new File(rootUrl.getPath() + "define" + File.separator + "app-" + app + ".yml");
try {
FileUtils.writeStringToFile(defineAppFile, ymlContent, StandardCharsets.UTF_8, false);
FileUtils.writeStringToFile(file, ymlContent, StandardCharsets.UTF_8, false);
} catch (Exception e) {
log.error(e.getMessage());
throw new RuntimeException("flush file " + defineAppPath + " error: " + e.getMessage());
throw new RuntimeException(e);
}
}
@Override
public void delete(String app) {
var classpath = Objects.requireNonNull(this.getClass().getClassLoader().getResource("")).getPath();
var defineAppPath = classpath + "define" + File.separator + "app-" + app + ".yml";
var defineAppFile = new File(defineAppPath);
if (!defineAppFile.exists() && appDefines.containsKey(app.toLowerCase())){
throw new CommonException("the app define file is not in current file server provider");
}
if (defineAppFile.exists() && defineAppFile.isFile()) {
defineAppFile.delete();
}
var rootUrl = this.getClass().getClassLoader().getResource("");
if (rootUrl == null) return;
var file = new File(rootUrl.getPath() + "define" + File.separator + "app-" + app + ".yml");
if (file.exists()) file.delete();
appDefines.remove(app.toLowerCase());
}
}
private class ObjectStoreAppDefineStoreImpl implements AppDefineStore {
@Override
public boolean loadAppDefines() {
var objectStoreService = getObjectStoreService();
Yaml yaml = new Yaml();
objectStoreService.list("define")
.forEach(it -> {
if (it.getInputStream() != null) {
var app = yaml.loadAs(it.getInputStream(), Job.class);
if (app != null) {
appDefines.put(app.getApp().toLowerCase(), app);
}
}
});
objectStoreService.list("define").forEach(it -> {
if (it.getInputStream() != null) {
var app = yaml.loadAs(it.getInputStream(), Job.class);
if (app != null) appDefines.put(app.getApp().toLowerCase(), app);
}
});
return true;
}
@Override
public String loadAppDefine(String app) {
var objectStoreService = getObjectStoreService();
var file = objectStoreService.download(getDefineAppPath(app));
if (isNull(file)) {
return null;
}
var file = objectStoreService.download("define/app-" + app + ".yml");
try {
return IOUtils.toString(file.getInputStream(), StandardCharsets.UTF_8);
return file != null ? IOUtils.toString(file.getInputStream(), StandardCharsets.UTF_8) : null;
} catch (IOException e) {
log.error("load app define from object store service error", e);
return null;
}
}
@Override
public void save(String app, String ymlContent) {
var objectStoreService = getObjectStoreService();
objectStoreService.upload(getDefineAppPath(app), IOUtils.toInputStream(ymlContent, StandardCharsets.UTF_8));
getObjectStoreService().upload("define/app-" + app + ".yml", IOUtils.toInputStream(ymlContent, StandardCharsets.UTF_8));
}
@Override
public void delete(String app) {
var objectStoreService = getObjectStoreService();
String defineAppPath = getDefineAppPath(app);
boolean exist = objectStoreService.isExist(defineAppPath);
if (!exist && appDefines.containsKey(app.toLowerCase())){
throw new CommonException("the app define file is not in current file server provider");
}
if (exist){
objectStoreService.remove(defineAppPath);
}
getObjectStoreService().remove("define/app-" + app + ".yml");
appDefines.remove(app.toLowerCase());
}
private ObjectStoreService getObjectStoreService() {
return SpringContextHolder.getBean(ObsObjectStoreServiceImpl.class);
}
private String getDefineAppPath(String app) {
return "define/app-" + app + ".yml";
}
}
private class DatabaseAppDefineStoreImpl implements AppDefineStore {
@Override
public boolean loadAppDefines() {
Yaml yaml = new Yaml();
List<Define> defines = defineDao.findAll();
for (Define define : defines) {
defineDao.findAll().forEach(define -> {
var app = yaml.loadAs(define.getContent(), Job.class);
if (app != null){
appDefines.put(define.getApp().toLowerCase(), app);
}
}
if (app != null) appDefines.put(define.getApp().toLowerCase(), app);
});
return true;
}
@Override
public String loadAppDefine(String app) {
Optional<Define> defineOptional = defineDao.findById(app);
return defineOptional.map(Define::getContent).orElse(null);
return defineDao.findById(app).map(Define::getContent).orElse(null);
}
@Override
@@ -797,13 +718,7 @@ public class AppServiceImpl implements AppService, InitializingBean {
@Override
public void delete(String app) {
Optional<Define> defineOptional = defineDao.findById(app);
if (defineOptional.isEmpty() && appDefines.containsKey(app.toLowerCase())){
throw new CommonException("the app define file is not in current file server provider");
}
if (defineOptional.isPresent()){
defineDao.deleteById(app);
}
defineDao.deleteById(app);
appDefines.remove(app.toLowerCase());
}
}
@@ -19,7 +19,6 @@ package org.apache.hertzbeat.manager.service.impl;
import static org.apache.hertzbeat.common.constants.ExportFileConstants.ExcelFile.FILE_SUFFIX;
import static org.apache.hertzbeat.common.constants.ExportFileConstants.ExcelFile.TYPE;
import com.fasterxml.jackson.core.type.TypeReference;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -45,6 +44,7 @@ import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.RegionUtil;
import org.springframework.stereotype.Service;
import tools.jackson.core.type.TypeReference;
/**
* Configure the import and export EXCEL format
@@ -169,7 +169,7 @@ public class ExcelImExportServiceImpl extends AbstractImExportServiceImpl{
default -> null;
};
}
private boolean getCellValueAsBoolean(Cell cell) {
if (cell == null) {
return false;
@@ -5,7 +5,9 @@
* The ASF licenses this file to You 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
*
* 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.
@@ -17,44 +19,35 @@ package org.apache.hertzbeat.manager.service.impl;
import static org.apache.hertzbeat.common.constants.ExportFileConstants.JsonFile.FILE_SUFFIX;
import static org.apache.hertzbeat.common.constants.ExportFileConstants.JsonFile.TYPE;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.springframework.stereotype.Service;
import tools.jackson.core.type.TypeReference;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* Configure the import and export JSON format
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class JsonImExportServiceImpl extends AbstractImExportServiceImpl {
private final ObjectMapper objectMapper;
@Override
public List<ExportMonitorDTO> parseImport(InputStream is) {
try {
return objectMapper.readValue(is, new TypeReference<>(){});
} catch (IOException ex) {
log.error("import monitor failed.", ex);
throw new RuntimeException("import monitor failed");
List<ExportMonitorDTO> result = JsonUtil.fromJson(is, new TypeReference<List<ExportMonitorDTO>>(){});
if (result == null) {
throw new RuntimeException("Parse JSON failed");
}
return result;
}
@Override
public void writeOs(List<ExportMonitorDTO> monitorList, OutputStream os) {
try {
objectMapper.writeValue(os, monitorList);
} catch (IOException ex) {
log.error("export monitor failed.", ex);
throw new RuntimeException("export monitor failed");
}
JsonUtil.toJson(monitorList, os);
}
@Override
@@ -17,8 +17,7 @@
package org.apache.hertzbeat.manager.service.impl;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import tools.jackson.core.type.TypeReference;
import java.lang.reflect.Type;
import org.apache.hertzbeat.common.constants.GeneralConfigTypeEnum;
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
@@ -33,23 +32,15 @@ import org.springframework.stereotype.Service;
@Service
public class MailGeneralConfigServiceImpl extends AbstractGeneralConfigServiceImpl<MailServerConfig> {
/**
* MailGeneralConfigServiceImpl's constructor creates an instance of this class
* through the default constructor or deserialization construction (setBeanProps).
* The parameter generalConfigDao is used for dao layer operation data,
* and objectMapper is used for object mapping.
* @param generalConfigDao dao layer operation data, needed to create an instance of this class
* @param objectMapper object mapping , needed to create an instance of this class
*/
public MailGeneralConfigServiceImpl(GeneralConfigDao generalConfigDao, ObjectMapper objectMapper) {
super(generalConfigDao, objectMapper);
public MailGeneralConfigServiceImpl(GeneralConfigDao generalConfigDao) {
super(generalConfigDao);
}
@Override
public String type() {
return GeneralConfigTypeEnum.email.name();
}
/**
* This method is used to get the TypeReference of NoticeSender type for subsequent processing.
* a TypeReference of NoticeSender type
@@ -17,8 +17,7 @@
package org.apache.hertzbeat.manager.service.impl;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import tools.jackson.core.type.TypeReference;
import java.lang.reflect.Type;
import org.apache.hertzbeat.common.entity.dto.ModelProviderConfig;
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
@@ -34,19 +33,12 @@ import org.springframework.stereotype.Service;
public class ModelProviderConfigServiceImpl extends AbstractGeneralConfigServiceImpl<ModelProviderConfig> {
private final ApplicationContext applicationContext;
/**
*
* <p>Constructor, passing in GeneralConfigDao, ObjectMapper and type.</p>
*
* @param generalConfigDao ConfigDao object
* @param objectMapper JSON tool object
*/
public ModelProviderConfigServiceImpl(ApplicationContext applicationContext, GeneralConfigDao generalConfigDao, ObjectMapper objectMapper) {
super(generalConfigDao, objectMapper);
public ModelProviderConfigServiceImpl(ApplicationContext applicationContext, GeneralConfigDao generalConfigDao) {
super(generalConfigDao);
this.applicationContext = applicationContext;
}
@Override
public String type() {
return GeneralConfigTypeEnum.provider.name();
@@ -18,8 +18,7 @@
package org.apache.hertzbeat.manager.service.impl;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import tools.jackson.core.type.TypeReference;
import java.lang.reflect.Type;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
@@ -37,14 +36,8 @@ import org.springframework.stereotype.Service;
@Slf4j
public class MuteGeneralConfigServiceImpl extends AbstractGeneralConfigServiceImpl<MuteConfig> {
/**
* <p>Constructor, passing in GeneralConfigDao, ObjectMapper and type.</p>
*
* @param generalConfigDao Dao object
* @param objectMapper JSON tool object
*/
protected MuteGeneralConfigServiceImpl(GeneralConfigDao generalConfigDao, ObjectMapper objectMapper) {
super(generalConfigDao, objectMapper);
protected MuteGeneralConfigServiceImpl(GeneralConfigDao generalConfigDao) {
super(generalConfigDao);
}
/**
@@ -17,12 +17,12 @@
package org.apache.hertzbeat.manager.service.impl;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import tools.jackson.core.type.TypeReference;
import com.obs.services.ObsClient;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.constants.GeneralConfigTypeEnum;
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.apache.hertzbeat.manager.pojo.dto.ObjectStoreConfigChangeEvent;
import org.apache.hertzbeat.manager.pojo.dto.ObjectStoreDTO;
import org.springframework.beans.factory.InitializingBean;
@@ -33,7 +33,7 @@ import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import javax.annotation.Resource;
import jakarta.annotation.Resource;
import java.lang.reflect.Type;
import java.net.URL;
@@ -52,14 +52,8 @@ public class ObjectStoreConfigServiceImpl extends
@Resource
private ApplicationContext ctx;
/**
* <p>Constructor, passing in GeneralConfigDao, ObjectMapper and type.</p>
*
* @param generalConfigDao configDao object
* @param objectMapper JSON tool object
*/
public ObjectStoreConfigServiceImpl(GeneralConfigDao generalConfigDao, ObjectMapper objectMapper) {
super(generalConfigDao, objectMapper);
public ObjectStoreConfigServiceImpl(GeneralConfigDao generalConfigDao) {
super(generalConfigDao);
}
@Override
@@ -94,7 +88,7 @@ public class ObjectStoreConfigServiceImpl extends
* init Huawei Cloud OBS
*/
private void initObs(ObjectStoreDTO<ObjectStoreDTO.ObsConfig> config) {
var obsConfig = objectMapper.convertValue(config.getConfig(), ObjectStoreDTO.ObsConfig.class);
var obsConfig = JsonUtil.convertValue(config.getConfig(), ObjectStoreDTO.ObsConfig.class);
Assert.hasText(obsConfig.getAccessKey(), "cannot find obs accessKey");
Assert.hasText(obsConfig.getSecretKey(), "cannot find obs secretKey");
Assert.hasText(obsConfig.getEndpoint(), "cannot find obs endpoint");
@@ -114,7 +108,7 @@ public class ObjectStoreConfigServiceImpl extends
/**
* Verify Huawei Cloud OBS endpoint domain name
* Only myhuaweicloud.com domain name is allowed
* Refer: https://console-intl.huaweicloud.com/apiexplorer/#/endpoint
* Refer: <a href="https://console-intl.huaweicloud.com/apiexplorer/#/endpoint">...</a>
*/
public void validateObsEndpoint(String endpoint) {
try {
@@ -17,6 +17,7 @@
package org.apache.hertzbeat.manager.service.impl;
import jakarta.annotation.PostConstruct;
import jakarta.persistence.criteria.Predicate;
import java.io.File;
import java.io.FileOutputStream;
@@ -45,7 +46,6 @@ import java.util.function.Consumer;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@@ -17,8 +17,7 @@
package org.apache.hertzbeat.manager.service.impl;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import tools.jackson.core.type.TypeReference;
import java.lang.reflect.Type;
import jakarta.annotation.Resource;
@@ -40,16 +39,8 @@ public class SmsGeneralConfigServiceImpl extends AbstractGeneralConfigServiceImp
@Resource
private ApplicationContext applicationContext;
/**
* SmsGeneralConfigServiceImpl's constructor creates an instance of this class
* through the default constructor or deserialization construction (setBeanProps).
* The parameter generalConfigDao is used for dao layer operation data,
* and objectMapper is used for object mapping.
* @param generalConfigDao dao layer operation data, needed to create an instance of this class
* @param objectMapper object mapping , needed to create an instance of this class
*/
public SmsGeneralConfigServiceImpl(GeneralConfigDao generalConfigDao, ObjectMapper objectMapper) {
super(generalConfigDao, objectMapper);
public SmsGeneralConfigServiceImpl(GeneralConfigDao generalConfigDao) {
super(generalConfigDao);
}
/**
@@ -59,12 +50,12 @@ public class SmsGeneralConfigServiceImpl extends AbstractGeneralConfigServiceImp
public void handler(SmsConfig smsConfig) {
applicationContext.publishEvent(new SmsConfigChangeEvent(applicationContext));
}
@Override
public String type() {
return GeneralConfigTypeEnum.sms.name();
}
/**
* This method is used to get the TypeReference of NoticeSender type for subsequent processing.
* a TypeReference of NoticeSender type
@@ -17,8 +17,7 @@
package org.apache.hertzbeat.manager.service.impl;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import tools.jackson.core.type.TypeReference;
import jakarta.annotation.Resource;
import java.lang.reflect.Type;
import java.util.Objects;
@@ -37,18 +36,11 @@ import org.springframework.stereotype.Service;
public class SystemGeneralConfigServiceImpl extends AbstractGeneralConfigServiceImpl<SystemConfig> {
@Resource
private ApplicationContext applicationContext;
/**
*
* <p>Constructor, passing in GeneralConfigDao, ObjectMapper and type.</p>
*
* @param generalConfigDao ConfigDao object
* @param objectMapper JSON tool object
*/
public SystemGeneralConfigServiceImpl(GeneralConfigDao generalConfigDao, ObjectMapper objectMapper) {
super(generalConfigDao, objectMapper);
public SystemGeneralConfigServiceImpl(GeneralConfigDao generalConfigDao) {
super(generalConfigDao);
}
@Override
public void handler(SystemConfig systemConfig) {
if (Objects.isNull(systemConfig)) {
@@ -58,12 +50,12 @@ public class SystemGeneralConfigServiceImpl extends AbstractGeneralConfigService
TimeZoneUtil.setTimeZoneAndLocale(systemConfig.getTimeZoneId(), systemConfig.getLocale());
applicationContext.publishEvent(new SystemConfigChangeEvent(applicationContext));
}
@Override
public String type() {
return GeneralConfigTypeEnum.system.name();
}
@Override
public TypeReference<SystemConfig> getTypeReference() {
return new TypeReference<>() {
@@ -17,13 +17,12 @@
package org.apache.hertzbeat.manager.service.impl;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.lang.reflect.Type;
import org.apache.hertzbeat.common.constants.GeneralConfigTypeEnum;
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
import org.apache.hertzbeat.manager.pojo.dto.SystemSecret;
import org.springframework.stereotype.Service;
import tools.jackson.core.type.TypeReference;
/**
* system config service impl
@@ -31,22 +30,15 @@ import org.springframework.stereotype.Service;
@Service
public class SystemSecretServiceImpl extends AbstractGeneralConfigServiceImpl<SystemSecret> {
/**
*
* <p>Constructor, passing in GeneralConfigDao, ObjectMapper and type.</p>
*
* @param generalConfigDao ConfigDao object
* @param objectMapper JSON tool object
*/
public SystemSecretServiceImpl(GeneralConfigDao generalConfigDao, ObjectMapper objectMapper) {
super(generalConfigDao, objectMapper);
public SystemSecretServiceImpl(GeneralConfigDao generalConfigDao) {
super(generalConfigDao);
}
@Override
public String type() {
return GeneralConfigTypeEnum.secret.name();
}
@Override
public TypeReference<SystemSecret> getTypeReference() {
return new TypeReference<>() {
@@ -17,8 +17,7 @@
package org.apache.hertzbeat.manager.service.impl;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import tools.jackson.core.type.TypeReference;
import jakarta.annotation.Resource;
import java.lang.reflect.Type;
import org.apache.hertzbeat.common.constants.GeneralConfigTypeEnum;
@@ -32,33 +31,26 @@ import org.springframework.stereotype.Service;
*/
@Service
public class TemplateConfigServiceImpl extends AbstractGeneralConfigServiceImpl<TemplateConfig> {
@Resource
private AppService appService;
/**
*
* <p>Constructor, passing in GeneralConfigDao, ObjectMapper and type.</p>
*
* @param generalConfigDao configDao object
* @param objectMapper JSON tool object
*/
public TemplateConfigServiceImpl(GeneralConfigDao generalConfigDao, ObjectMapper objectMapper) {
super(generalConfigDao, objectMapper);
public TemplateConfigServiceImpl(GeneralConfigDao generalConfigDao) {
super(generalConfigDao);
}
@Override
public void handler(TemplateConfig templateConfig) {
if (templateConfig != null) {
appService.updateCustomTemplateConfig(templateConfig);
}
}
@Override
public String type() {
return GeneralConfigTypeEnum.template.name();
}
@Override
protected TypeReference<TemplateConfig> getTypeReference() {
return new TypeReference<>() {
@@ -22,7 +22,7 @@ import static org.apache.hertzbeat.common.constants.CommonConstants.FAIL_CODE;
import static org.apache.hertzbeat.common.constants.CommonConstants.MONITOR_CONFLICT_CODE;
import static org.apache.hertzbeat.common.constants.CommonConstants.PARAM_INVALID_CODE;
import java.util.Objects;
import javax.validation.ConstraintViolationException;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.apache.hertzbeat.common.entity.dto.Message;
import org.apache.hertzbeat.common.support.exception.CommonException;
@@ -86,7 +86,7 @@ public class GlobalExceptionHandler {
Message<Void> message = Message.fail(FAIL_CODE, exception.getMessage());
return ResponseEntity.ok(message);
}
/**
* processing parameter error
* @param exception parameter exception
@@ -1,75 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.hertzbeat.manager.component.listener;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
import org.apache.hertzbeat.common.support.event.SystemConfigChangeEvent;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
/**
* test case for {@link TimeZoneListener}
*/
@ExtendWith(MockitoExtension.class)
class TimeZoneListenerTest {
@Mock
private ObjectMapper objectMapper;
@InjectMocks
private TimeZoneListener timeZoneListener;
@Mock
private SystemConfigChangeEvent event;
@BeforeEach
void setUp() {
ReflectionTestUtils.setField(timeZoneListener, "objectMapper", objectMapper);
}
@Test
void testOnEvent() {
when(objectMapper.setTimeZone(any(TimeZone.class))).thenReturn(objectMapper);
when(objectMapper.setDateFormat(any(SimpleDateFormat.class))).thenReturn(objectMapper);
Object eventSource = new Object();
when(event.getSource()).thenReturn(eventSource);
timeZoneListener.onEvent(event);
SimpleDateFormat expectedDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX");
expectedDateFormat.setTimeZone(TimeZone.getDefault());
verify(objectMapper).setTimeZone(TimeZone.getDefault());
verify(objectMapper).setDateFormat(expectedDateFormat);
}
}
@@ -91,7 +91,7 @@ class GeneralConfigControllerTest {
mockMvc.perform(put("/api/config/template/appName")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"templateKey\":\"templateValue\"}"))
.content("{\"templateKey\":\"templateValue\",\"hide\":true}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE));
}
@@ -125,7 +125,7 @@ class StatusPageControllerTest {
mockMvc.perform(post("/api/status/page/component")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"name\":\"New Component\"}")
.content("{\"name\":\"New Component\",\"method\":0,\"configState\":0,\"state\":0}")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
@@ -137,7 +137,7 @@ class StatusPageControllerTest {
mockMvc.perform(put("/api/status/page/component")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"name\":\"Updated Component\"}")
.content("{\"name\":\"Updated Component\",\"method\":0,\"configState\":0,\"state\":0}")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))

Some files were not shown because too many files have changed in this diff Show More